feat(i18n): 完成全量 UI 文本国际化,替换所有硬编码中文为 AppLocalizations 调用
- core/localization: 新增约 60 个翻译键(含参数化方法),中英双语覆盖 - shared/widgets: CommonDialog 默认参数国际化 - features/home: 完成页操作步骤指引、状态栏串口连接状态、程序列表状态标签 - features/programs: 表头状态列、表单验证提示、导入/模板操作反馈、删除确认(参数化) - features/program_detail: 步骤列表/表单标题、删除确认、速度档位显示(参数化) - features/device: run_state_provider 错误消息改为错误码 - features/settings: 升级页、密码面板、语言面板、U盘导入面板、串口配置面板全部替换 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -116,11 +116,12 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
}
|
||||
|
||||
Widget _buildUpgradeContent() {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
Text(
|
||||
'软件升级',
|
||||
l10n?.upgrade ?? '软件升级',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 18,
|
||||
@@ -139,7 +140,7 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
Icon(Icons.info_outline, color: AppTheme.primaryColor),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'当前版本: $_currentVersion',
|
||||
'${l10n?.currentVersion ?? '当前版本'}: $_currentVersion',
|
||||
style: TextStyle(color: AppTheme.textPrimary),
|
||||
),
|
||||
],
|
||||
@@ -147,13 +148,13 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
CommonButton(
|
||||
text: '检查更新',
|
||||
text: l10n?.checkUpdate ?? '检查更新',
|
||||
icon: Icons.refresh,
|
||||
type: ButtonType.primary,
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('已是最新版本'),
|
||||
content: Text(l10n?.latestVersion ?? '已是最新版本'),
|
||||
backgroundColor: AppTheme.successColor,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/localization/locale_provider.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
|
||||
@@ -13,6 +14,7 @@ class LanguagePanel extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final locale = ref.watch(localeProvider);
|
||||
final currentLang = locale.languageCode;
|
||||
|
||||
@@ -20,7 +22,7 @@ class LanguagePanel extends ConsumerWidget {
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
_SectionCard(
|
||||
title: '语言设置',
|
||||
title: l10n?.languageSettings ?? '语言设置',
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -46,7 +48,7 @@ class LanguagePanel extends ConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
'切换语言后立即生效',
|
||||
l10n?.switchLanguageEffect ?? '切换语言后立即生效',
|
||||
style: TextStyle(color: AppTheme.textSecondary, fontSize: 12),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../shared/services/toast_service.dart';
|
||||
import '../services/settings_service.dart';
|
||||
@@ -33,20 +34,21 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (_submitting) return;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final oldPwd = _oldCtrl.text.trim();
|
||||
final newPwd = _newCtrl.text.trim();
|
||||
final confirmPwd = _confirmCtrl.text.trim();
|
||||
|
||||
if (oldPwd.isEmpty || newPwd.isEmpty || confirmPwd.isEmpty) {
|
||||
setState(() => _error = '请填写所有字段');
|
||||
setState(() => _error = l10n?.fillAllFields ?? '请填写所有字段');
|
||||
return;
|
||||
}
|
||||
if (newPwd.length < 6) {
|
||||
setState(() => _error = '新密码至少6位字符');
|
||||
setState(() => _error = l10n?.newPwdMinLength ?? '新密码至少6位字符');
|
||||
return;
|
||||
}
|
||||
if (newPwd != confirmPwd) {
|
||||
setState(() => _error = '两次输入的新密码不一致');
|
||||
setState(() => _error = l10n?.passwordMismatch ?? '两次输入的新密码不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,7 +62,7 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_submitting = false;
|
||||
_error = '原密码错误';
|
||||
_error = l10n?.oldPasswordError ?? '原密码错误';
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -73,14 +75,15 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
_oldCtrl.clear();
|
||||
_newCtrl.clear();
|
||||
_confirmCtrl.clear();
|
||||
ToastService.showSuccess(context, '密码已修改');
|
||||
ToastService.showSuccess(context, l10n?.passwordChanged ?? '密码已修改');
|
||||
} else {
|
||||
ToastService.showError(context, '密码修改失败');
|
||||
ToastService.showError(context, l10n?.passwordChangeFailed ?? '密码修改失败');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
@@ -95,7 +98,7 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'密码修改',
|
||||
l10n?.password ?? '密码修改',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 16,
|
||||
@@ -105,20 +108,20 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
const Divider(),
|
||||
_field(
|
||||
controller: _oldCtrl,
|
||||
label: '原密码',
|
||||
hint: '请输入当前密码',
|
||||
label: l10n?.oldPassword ?? '原密码',
|
||||
hint: l10n?.enterCurrentPassword ?? '请输入当前密码',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_field(
|
||||
controller: _newCtrl,
|
||||
label: '新密码',
|
||||
hint: '至少6位字符',
|
||||
label: l10n?.newPassword ?? '新密码',
|
||||
hint: l10n?.passwordMinLength ?? '至少6位字符',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_field(
|
||||
controller: _confirmCtrl,
|
||||
label: '确认新密码',
|
||||
hint: '再次输入新密码',
|
||||
label: l10n?.confirmNewPassword ?? '确认新密码',
|
||||
hint: l10n?.enterNewPassword ?? '再次输入新密码',
|
||||
),
|
||||
if (_error != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
@@ -140,7 +143,7 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
_confirmCtrl.clear();
|
||||
setState(() => _error = null);
|
||||
},
|
||||
child: const Text('重置'),
|
||||
child: Text(l10n?.reset ?? '重置'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton.icon(
|
||||
@@ -155,7 +158,7 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.check, size: 18),
|
||||
label: const Text('确认'),
|
||||
label: Text(l10n?.confirm ?? '确认'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -166,7 +169,7 @@ class _PasswordPanelState extends ConsumerState<PasswordPanel> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Text(
|
||||
'默认密码为 123456',
|
||||
l10n?.defaultPassword ?? '默认密码为 123456',
|
||||
style: TextStyle(color: AppTheme.textSecondary, fontSize: 12),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:usb_serial/usb_serial.dart';
|
||||
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../device/models/serial_config.dart';
|
||||
import '../../device/providers/serial_provider.dart';
|
||||
@@ -48,14 +49,14 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _loadingDevices = false);
|
||||
_showSnack('扫描设备失败: $e', AppTheme.errorColor);
|
||||
_showSnack('${AppLocalizations.of(context)?.scanFailed ?? '扫描设备失败'}: $e', AppTheme.errorColor);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _connect() async {
|
||||
final device = _selectedDevice;
|
||||
if (device == null) {
|
||||
_showSnack('请先选择串口设备', AppTheme.warningColor);
|
||||
_showSnack(AppLocalizations.of(context)?.selectSerialFirst ?? '请先选择串口设备', AppTheme.warningColor);
|
||||
return;
|
||||
}
|
||||
setState(() => _operating = true);
|
||||
@@ -65,7 +66,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
if (!mounted) return;
|
||||
setState(() => _operating = false);
|
||||
_showSnack(
|
||||
ok ? '连接成功' : '连接失败: ${service.lastError ?? "未知错误"}',
|
||||
ok ? (AppLocalizations.of(context)?.connectSuccess ?? '连接成功') : ('${AppLocalizations.of(context)?.connectFailed ?? '连接失败'}: ${service.lastError ?? (AppLocalizations.of(context)?.unknownError ?? '未知错误')}'),
|
||||
ok ? AppTheme.successColor : AppTheme.errorColor,
|
||||
);
|
||||
}
|
||||
@@ -76,13 +77,13 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
await service.disconnect();
|
||||
if (!mounted) return;
|
||||
setState(() => _operating = false);
|
||||
_showSnack('已断开串口', AppTheme.infoColor);
|
||||
_showSnack(AppLocalizations.of(context)?.disconnected ?? '已断开串口', AppTheme.infoColor);
|
||||
}
|
||||
|
||||
Future<void> _testConnection() async {
|
||||
final service = ref.read(serialPortServiceProvider);
|
||||
if (!service.isConnected) {
|
||||
_showSnack('请先连接串口', AppTheme.warningColor);
|
||||
_showSnack(AppLocalizations.of(context)?.connectFirst ?? '请先连接串口', AppTheme.warningColor);
|
||||
return;
|
||||
}
|
||||
final msgService = ref.read(deviceMessageServiceProvider);
|
||||
@@ -94,7 +95,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
));
|
||||
if (!mounted) return;
|
||||
_showSnack(
|
||||
ok ? '已发送 device_info 查询' : '发送失败',
|
||||
ok ? (AppLocalizations.of(context)?.sendTestFrame ?? '已发送测试查询') : (AppLocalizations.of(context)?.connectFailed ?? '发送失败'),
|
||||
ok ? AppTheme.successColor : AppTheme.errorColor,
|
||||
);
|
||||
}
|
||||
@@ -107,25 +108,26 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final config = ref.watch(serialConfigProvider);
|
||||
final state = ref.watch(serialPortServiceProvider).state;
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(0),
|
||||
children: [
|
||||
_buildStatusCard(state),
|
||||
_buildStatusCard(state, l10n),
|
||||
const SizedBox(height: 16),
|
||||
_buildDeviceCard(),
|
||||
_buildDeviceCard(l10n),
|
||||
const SizedBox(height: 16),
|
||||
_buildParamCard(config),
|
||||
_buildParamCard(config, l10n),
|
||||
const SizedBox(height: 16),
|
||||
_buildActionRow(),
|
||||
_buildActionRow(l10n),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// -- 状态卡 ----------------------------------------------------------
|
||||
|
||||
Widget _buildStatusCard(SerialConnectionState state) {
|
||||
Widget _buildStatusCard(SerialConnectionState state, AppLocalizations? l10n) {
|
||||
final color = switch (state) {
|
||||
SerialConnectionState.connected => AppTheme.successColor,
|
||||
SerialConnectionState.connecting => AppTheme.warningColor,
|
||||
@@ -133,10 +135,10 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
SerialConnectionState.disconnected => AppTheme.idleColor,
|
||||
};
|
||||
final text = switch (state) {
|
||||
SerialConnectionState.connected => '已连接',
|
||||
SerialConnectionState.connecting => '连接中...',
|
||||
SerialConnectionState.error => '错误',
|
||||
SerialConnectionState.disconnected => '未连接',
|
||||
SerialConnectionState.connected => l10n?.serialConnected ?? '已连接',
|
||||
SerialConnectionState.connecting => l10n?.serialConnecting ?? '连接中...',
|
||||
SerialConnectionState.error => l10n?.serialError ?? '错误',
|
||||
SerialConnectionState.disconnected => l10n?.serialDisconnected ?? '未连接',
|
||||
};
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -153,7 +155,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text('串口状态: $text',
|
||||
Text('${l10n?.serialStatus ?? '串口状态'}: $text',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 16,
|
||||
@@ -166,13 +168,13 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
|
||||
// -- 设备列表 --------------------------------------------------------
|
||||
|
||||
Widget _buildDeviceCard() {
|
||||
Widget _buildDeviceCard(AppLocalizations? l10n) {
|
||||
return _SectionCard(
|
||||
title: '可用串口设备',
|
||||
title: l10n?.availableSerialDevices ?? '可用串口设备',
|
||||
trailing: TextButton.icon(
|
||||
onPressed: _loadingDevices ? null : _refreshDevices,
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
label: const Text('刷新'),
|
||||
label: Text(l10n?.refresh ?? '刷新'),
|
||||
),
|
||||
child: _loadingDevices
|
||||
? const Padding(
|
||||
@@ -190,16 +192,17 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
}
|
||||
|
||||
Widget _buildEmptyDevice() {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(Icons.usb_off, size: 40, color: AppTheme.idleColor),
|
||||
const SizedBox(height: 8),
|
||||
Text('未检测到 USB 串口设备',
|
||||
Text(l10n?.noSerialDevice ?? '未检测到 USB 串口设备',
|
||||
style: TextStyle(color: AppTheme.textSecondary)),
|
||||
const SizedBox(height: 4),
|
||||
Text('请确认下位机已上电并通过 USB 接入设备',
|
||||
Text(l10n?.serialDeviceHint ?? '请确认下位机已上电并通过 USB 接入设备',
|
||||
style: TextStyle(color: AppTheme.textSecondary, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
@@ -255,45 +258,45 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
|
||||
// -- 参数配置 --------------------------------------------------------
|
||||
|
||||
Widget _buildParamCard(SerialConfig config) {
|
||||
Widget _buildParamCard(SerialConfig config, AppLocalizations? l10n) {
|
||||
return _SectionCard(
|
||||
title: '串口参数',
|
||||
title: l10n?.serialParams ?? '串口参数',
|
||||
child: Column(
|
||||
children: [
|
||||
_baudRateRow(config),
|
||||
_baudRateRow(config, l10n),
|
||||
_dropdownRow<int>(
|
||||
label: '数据位',
|
||||
label: l10n?.dataBits ?? '数据位',
|
||||
value: config.dataBits,
|
||||
options: const [5, 6, 7, 8],
|
||||
display: (v) => '$v',
|
||||
onChanged: (v) => _updateConfig((c) => c.copyWith(dataBits: v)),
|
||||
),
|
||||
_dropdownRow<int>(
|
||||
label: '停止位',
|
||||
label: l10n?.stopBits ?? '停止位',
|
||||
value: config.stopBits,
|
||||
options: const [1, 2],
|
||||
display: (v) => '$v',
|
||||
onChanged: (v) => _updateConfig((c) => c.copyWith(stopBits: v)),
|
||||
),
|
||||
_dropdownRow<SerialParity>(
|
||||
label: '校验位',
|
||||
label: l10n?.parity ?? '校验位',
|
||||
value: config.parity,
|
||||
options: SerialParity.values,
|
||||
display: (v) => switch (v) {
|
||||
SerialParity.none => '无',
|
||||
SerialParity.odd => '奇',
|
||||
SerialParity.even => '偶',
|
||||
SerialParity.mark => '标记',
|
||||
SerialParity.space => '空',
|
||||
SerialParity.none => l10n?.parityNone ?? '无',
|
||||
SerialParity.odd => l10n?.parityOdd ?? '奇',
|
||||
SerialParity.even => l10n?.parityEven ?? '偶',
|
||||
SerialParity.mark => l10n?.parityMark ?? '标记',
|
||||
SerialParity.space => l10n?.paritySpace ?? '空',
|
||||
},
|
||||
onChanged: (v) => _updateConfig((c) => c.copyWith(parity: v)),
|
||||
),
|
||||
_dropdownRow<SerialFlowControl>(
|
||||
label: '流控',
|
||||
label: l10n?.flowControl ?? '流控',
|
||||
value: config.flowControl,
|
||||
options: SerialFlowControl.values,
|
||||
display: (v) => switch (v) {
|
||||
SerialFlowControl.none => '无',
|
||||
SerialFlowControl.none => l10n?.parityNone ?? '无',
|
||||
SerialFlowControl.rtsCts => 'RTS/CTS',
|
||||
SerialFlowControl.xonXoff => 'XON/XOFF',
|
||||
SerialFlowControl.dtrDsr => 'DTR/DSR',
|
||||
@@ -304,7 +307,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'参数修改后自动保存',
|
||||
l10n?.autoSaveParams ?? '参数修改后自动保存',
|
||||
style: TextStyle(color: AppTheme.textSecondary, fontSize: 12),
|
||||
),
|
||||
),
|
||||
@@ -313,7 +316,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _baudRateRow(SerialConfig config) {
|
||||
Widget _baudRateRow(SerialConfig config, AppLocalizations? l10n) {
|
||||
final ctrl = TextEditingController(text: config.baudRate.toString());
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
@@ -322,7 +325,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
SizedBox(
|
||||
width: 80,
|
||||
child:
|
||||
Text('波特率', style: TextStyle(color: AppTheme.textPrimary))),
|
||||
Text(l10n?.baudRate ?? '波特率', style: TextStyle(color: AppTheme.textPrimary))),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: 140,
|
||||
@@ -425,7 +428,7 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
|
||||
// -- 操作按钮 --------------------------------------------------------
|
||||
|
||||
Widget _buildActionRow() {
|
||||
Widget _buildActionRow(AppLocalizations? l10n) {
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
@@ -433,19 +436,19 @@ class _SerialConfigPanelState extends ConsumerState<SerialConfigPanel> {
|
||||
ElevatedButton.icon(
|
||||
onPressed: _operating ? null : _connect,
|
||||
icon: const Icon(Icons.link),
|
||||
label: const Text('连接'),
|
||||
label: Text(l10n?.connect ?? '连接'),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
_operating ? null : _disconnect,
|
||||
style: ElevatedButton.styleFrom(backgroundColor: AppTheme.errorColor),
|
||||
icon: const Icon(Icons.link_off),
|
||||
label: const Text('断开'),
|
||||
label: Text(l10n?.disconnect ?? '断开'),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _operating ? null : _testConnection,
|
||||
icon: const Icon(Icons.send),
|
||||
label: const Text('发送测试帧'),
|
||||
label: Text(l10n?.sendTestFrame ?? '发送测试帧'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../core/localization/app_localizations.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../shared/services/toast_service.dart';
|
||||
import '../services/usb_detection_service.dart';
|
||||
@@ -21,18 +22,20 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
Future<void> _detect() async {
|
||||
if (_detecting) return;
|
||||
setState(() => _detecting = true);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final connected = await UsbDetectionService.instance.detectUsb();
|
||||
if (!mounted) return;
|
||||
setState(() => _detecting = false);
|
||||
if (connected) {
|
||||
ToastService.showInfo(context, '正在检测U盘...');
|
||||
ToastService.showInfo(context, l10n?.detectingUsb ?? '正在检测U盘...');
|
||||
} else {
|
||||
ToastService.showWarning(context, '未检测到U盘');
|
||||
ToastService.showWarning(context, l10n?.usbNotDetected ?? '未检测到U盘');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final state = ref.watch(_usbStateProvider);
|
||||
final connected = state.maybeWhen(
|
||||
data: (s) => s.isConnected,
|
||||
@@ -67,7 +70,7 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
connected ? 'U盘已连接' : '未检测到U盘',
|
||||
connected ? (l10n?.usbConnected ?? 'U盘已连接') : (l10n?.usbNotDetected ?? '未检测到U盘'),
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 16,
|
||||
@@ -80,8 +83,8 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
connected
|
||||
? '挂载路径: ${path ?? "未知"}'
|
||||
: '请插入U盘后点击"重新检测"',
|
||||
? '${l10n?.mountPath ?? '挂载路径'}: ${path ?? "?"}'
|
||||
: (l10n?.insertUsb ?? '请插入U盘后重试'),
|
||||
style: TextStyle(
|
||||
color: AppTheme.textSecondary, fontSize: 13),
|
||||
),
|
||||
@@ -98,13 +101,13 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh, size: 18),
|
||||
label: const Text('重新检测'),
|
||||
label: Text(l10n?.reDetect ?? '重新检测'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton.icon(
|
||||
onPressed: connected ? () => _import(path) : null,
|
||||
icon: const Icon(Icons.download, size: 18),
|
||||
label: const Text('导入程序'),
|
||||
label: Text(l10n?.importProgram ?? '导入程序'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -123,7 +126,7 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'使用说明',
|
||||
l10n?.usageInstructions ?? '使用说明',
|
||||
style: TextStyle(
|
||||
color: AppTheme.textPrimary,
|
||||
fontSize: 14,
|
||||
@@ -131,9 +134,9 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_bullet('将程序文件 (.json) 放入 U盘根目录的 programs 文件夹'),
|
||||
_bullet('插入 U盘后点击"重新检测"'),
|
||||
_bullet('检测成功后点击"导入程序"加载程序列表'),
|
||||
_bullet(l10n?.usbUsageStep1 ?? '将程序文件 (.json) 放入 U盘根目录的 programs 文件夹'),
|
||||
_bullet(l10n?.usbUsageStep2 ?? '插入 U盘后点击"重新检测"'),
|
||||
_bullet(l10n?.usbUsageStep3 ?? '检测成功后点击"导入程序"加载程序列表'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -142,11 +145,12 @@ class _UsbImportPanelState extends ConsumerState<UsbImportPanel> {
|
||||
}
|
||||
|
||||
void _import(String? path) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
if (path == null) {
|
||||
ToastService.showWarning(context, 'U盘路径无效');
|
||||
ToastService.showWarning(context, l10n?.usbPathInvalid ?? 'U盘路径无效');
|
||||
return;
|
||||
}
|
||||
ToastService.showInfo(context, '正在导入程序...');
|
||||
ToastService.showInfo(context, l10n?.importingPrograms ?? '正在导入程序...');
|
||||
// TODO: 接入 program_import_service 实现真正的导入流程
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user