修改名字接口

This commit is contained in:
2026-11-06 08:44:47 +08:00
parent 8eea42b75d
commit e6165b180b
3 changed files with 156 additions and 31 deletions

View File

@@ -0,0 +1,61 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/usecases/update_device_alias_usecase.dart';
import 'rename_device_state.dart';
/// 设备改名 Cubit
///
/// 调用链:UI → RenameDeviceCubit → UpdateDeviceAliasUseCase → Repository → DataSource,
/// 页面不再直接依赖用例或数据源。
class RenameDeviceCubit extends Cubit<RenameDeviceState> {
final UpdateDeviceAliasUseCase _updateDeviceAlias;
RenameDeviceCubit(this._updateDeviceAlias) : super(const RenameDeviceState());
/// 下发修改设备别名(后端只写 deviceAlias,deviceName 保持不变)
Future<void> rename({required String deviceId, required String alias}) async {
if (state.isSubmitting) return;
emit(
RenameDeviceState(
isSubmitting: true,
deviceId: deviceId,
alias: alias,
),
);
final result = await _updateDeviceAlias.execute(
deviceId: deviceId,
deviceAlias: alias,
);
result.fold(
(failure) => emit(
RenameDeviceState(
errorMessage: failure.message,
deviceId: deviceId,
alias: alias,
),
),
(updated) => emit(
updated
? RenameDeviceState(
isSuccess: true,
deviceId: deviceId,
alias: alias,
)
: RenameDeviceState(
errorMessage: '',
deviceId: deviceId,
alias: alias,
),
),
);
}
/// 结果已被界面消费,复位避免重复弹提示
void reset() {
if (state.isSubmitting) return;
emit(const RenameDeviceState());
}
}

View File

@@ -0,0 +1,39 @@
import 'package:equatable/equatable.dart';
/// 设备改名(写 deviceAlias)提交状态
///
/// 一次性结果用 [isSuccess] / [errorMessage] 表达:
/// [errorMessage] 非 null 即失败,空串表示只知失败、文案由 UI 套本地化提示。
class RenameDeviceState extends Equatable {
/// 请求进行中(用于拦截重复提交)
final bool isSubmitting;
/// 后端已成功写入 deviceAlias
final bool isSuccess;
/// 失败原因;null 表示尚无结果
final String? errorMessage;
/// 本次操作的设备 ID
final String? deviceId;
/// 本次提交的新别名
final String? alias;
const RenameDeviceState({
this.isSubmitting = false,
this.isSuccess = false,
this.errorMessage,
this.deviceId,
this.alias,
});
@override
List<Object?> get props => [
isSubmitting,
isSuccess,
errorMessage,
deviceId,
alias,
];
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
/// 修改设备名称(设计稿:设备卡片铅笔图标 → 底部弹窗)
///
@@ -63,8 +64,18 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
static const Color _fieldBorder = Color(0xFFDCEBFA);
static const Color _divider = Color(0xFFF0F4FA);
/// 快捷命名(最后一项是「自定义」:清空输入,自由填写)
static const List<String> _presets = ['1号机', '2号机', '北区', '南区', '测试车', '自定义'];
/// 快捷命名代码(最后一项 custom:清空输入,自由填写)
static const List<String> _presetKeys = [
'preset_1',
'preset_2',
'preset_north',
'preset_south',
'preset_test',
'preset_custom',
];
static String _presetLabel(AppLocalizations l10n, String key) =>
l10n.translate('device_list_v2.$key');
late final TextEditingController _controller = TextEditingController(
text: widget.currentName,
@@ -104,10 +115,11 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
}
/// 快捷命名:前五项作为名称填入(可继续编辑),自定义则清空待输入
void _applyPreset(String preset) {
void _applyPreset(String key) {
final label = _presetLabel(AppLocalizations.of(context), key);
setState(() {
_empty = false;
_controller.text = preset == '自定义' ? '' : preset;
_controller.text = key == 'preset_custom' ? '' : label;
_controller.selection = TextSelection.collapsed(
offset: _controller.text.length,
);
@@ -117,6 +129,7 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final keyboard = MediaQuery.of(context).viewInsets.bottom;
return Container(
decoration: const BoxDecoration(
@@ -141,9 +154,9 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
const SizedBox(height: 16),
const Divider(height: 1, thickness: 0.6, color: _divider),
const SizedBox(height: 16),
const Text(
'设备名称',
style: TextStyle(
Text(
l10n.translate('device_list_v2.rename_label'),
style: const TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w700,
color: _navy,
@@ -154,24 +167,26 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
const SizedBox(height: 8),
Text(
_empty
? '名称不能为空'
: '支持中文、英文、数字和常用符号,建议 1-${widget.maxLength} 个字符',
? l10n.translate('device_list_v2.rename_empty')
: l10n
.translate('device_list_v2.rename_hint')
.replaceAll('%s', '${widget.maxLength}'),
style: TextStyle(
fontSize: 11.5,
color: _empty ? _danger : _hint,
),
),
const SizedBox(height: 16),
const Text(
'快捷命名',
style: TextStyle(
Text(
l10n.translate('device_list_v2.rename_quick'),
style: const TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w700,
color: _navy,
),
),
const SizedBox(height: 10),
_buildPresets(),
_buildPresets(l10n),
const SizedBox(height: 20),
_buildActions(),
const SizedBox(height: 12),
@@ -201,12 +216,14 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
Widget _buildHeader() {
return Row(
children: [
const Expanded(
Expanded(
child: Text(
'修改设备名称',
AppLocalizations.of(
context,
).translate('device_list_v2.rename_title'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: _navy,
@@ -333,14 +350,16 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
color: _navy,
),
cursorColor: _blue,
decoration: const InputDecoration(
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
hintText: '请输入设备名称',
hintStyle: TextStyle(fontSize: 16, color: _hint),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
hintText: AppLocalizations.of(
context,
).translate('device_list_v2.rename_field_hint'),
hintStyle: const TextStyle(fontSize: 16, color: _hint),
),
),
),
@@ -372,14 +391,14 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
}
/// 快捷命名胶囊
Widget _buildPresets() {
Widget _buildPresets(AppLocalizations l10n) {
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final p in _presets)
for (final key in _presetKeys)
GestureDetector(
onTap: () => _applyPreset(p),
onTap: () => _applyPreset(key),
behavior: HitTestBehavior.opaque,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 9),
@@ -389,7 +408,7 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
border: Border.all(color: _fieldBorder),
),
child: Text(
p,
_presetLabel(l10n, key),
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
@@ -416,9 +435,11 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
color: _softBlue,
borderRadius: BorderRadius.circular(14),
),
child: const Text(
'取消',
style: TextStyle(
child: Text(
AppLocalizations.of(
context,
).translate('device_list_v2.action_cancel'),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: _blue,
@@ -446,9 +467,11 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
),
],
),
child: const Text(
'保存',
style: TextStyle(
child: Text(
AppLocalizations.of(
context,
).translate('device_list_v2.action_save'),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
@@ -469,7 +492,9 @@ class _RenameDeviceSheetState extends State<_RenameDeviceSheet> {
const SizedBox(width: 6),
Flexible(
child: Text(
'仅修改显示名称,不影响 SN、绑定关系和设备权限',
AppLocalizations.of(
context,
).translate('device_list_v2.rename_footer'),
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 11.5, color: _hint),
),