142 lines
4.6 KiB
Dart
142 lines
4.6 KiB
Dart
import 'package:cupertino_ui/cupertino_ui.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
|
||
import '../../../core/widgets/feature_ui.dart';
|
||
import '../../../core/widgets/settings_ui.dart';
|
||
import '../../../l10n/app_localizations.dart';
|
||
import 'device_manage_controller.dart';
|
||
|
||
/// 绑定设备页(手动输入 SN)。
|
||
///
|
||
/// 提供 SN / 备注名输入、「确认绑定」与「扫码绑定」两个入口。
|
||
/// 扫码入口跳转 `/device/scan`,扫描结果回填到 SN 输入框。
|
||
class DeviceBindPage extends ConsumerStatefulWidget {
|
||
const DeviceBindPage({super.key, this.initialSn});
|
||
|
||
/// 由扫码页回传的初始 SN(可选)。
|
||
final String? initialSn;
|
||
|
||
@override
|
||
ConsumerState<DeviceBindPage> createState() => _DeviceBindPageState();
|
||
}
|
||
|
||
class _DeviceBindPageState extends ConsumerState<DeviceBindPage> {
|
||
late final TextEditingController _snController;
|
||
final TextEditingController _remarkController = TextEditingController();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_snController = TextEditingController(text: widget.initialSn ?? '');
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_snController.dispose();
|
||
_remarkController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final l10n = AppLocalizations.of(context);
|
||
final action = ref.watch(deviceManageControllerProvider);
|
||
|
||
// 一次性错误提示:展示后立即消费。
|
||
ref.listen<DeviceManageState>(deviceManageControllerProvider, (prev, next) {
|
||
final alert = next.alert;
|
||
if (alert == null) return;
|
||
ref.read(deviceManageControllerProvider.notifier).consumeAlert();
|
||
AppDialogs.alert(context, message: alert, confirmText: l10n.commonConfirm);
|
||
});
|
||
|
||
return CupertinoPageScaffold(
|
||
navigationBar: CupertinoNavigationBar(
|
||
middle: Text(l10n.deviceBindTitle),
|
||
leading: CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
child: const Icon(CupertinoIcons.back),
|
||
onPressed: () => context.pop(),
|
||
),
|
||
),
|
||
child: SafeArea(
|
||
child: ListView(
|
||
padding: const EdgeInsets.only(top: 8, bottom: 24),
|
||
children: [
|
||
CardBox(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child: Column(
|
||
children: [
|
||
LabeledField(
|
||
label: l10n.deviceBindSnLabel,
|
||
controller: _snController,
|
||
placeholder: l10n.deviceBindSnHint,
|
||
keyboardType: TextInputType.text,
|
||
),
|
||
LabeledField(
|
||
label: l10n.deviceBindRemarkLabel,
|
||
controller: _remarkController,
|
||
placeholder: l10n.deviceBindRemarkHint,
|
||
maxLength: 20,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Padding(
|
||
padding: const EdgeInsets.fromLTRB(22, 6, 22, 2),
|
||
child: Text(
|
||
l10n.deviceBindTip,
|
||
style: const TextStyle(fontSize: 12, color: AppColors.sub, height: 1.5),
|
||
),
|
||
),
|
||
const SizedBox(height: 6),
|
||
PrimaryButton(
|
||
text: l10n.deviceBindConfirm,
|
||
loading: action.submitting,
|
||
onPressed: () => _submit(l10n),
|
||
),
|
||
SecondaryButton(
|
||
text: l10n.deviceBindScan,
|
||
icon: CupertinoIcons.qrcode_viewfinder,
|
||
onPressed: action.submitting ? null : () => _openScanner(l10n),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 校验 SN 后提交绑定,成功则提示并返回上一页。
|
||
Future<void> _submit(AppLocalizations l10n) async {
|
||
final sn = _snController.text.trim();
|
||
if (sn.isEmpty) {
|
||
await AppDialogs.alert(
|
||
context,
|
||
message: l10n.deviceBindSnEmpty,
|
||
confirmText: l10n.commonConfirm,
|
||
);
|
||
return;
|
||
}
|
||
|
||
final ok = await ref.read(deviceManageControllerProvider.notifier).bind(
|
||
sn,
|
||
remark: _remarkController.text,
|
||
);
|
||
if (!ok || !mounted) return;
|
||
await AppDialogs.alert(
|
||
context,
|
||
message: l10n.deviceBindSuccess,
|
||
confirmText: l10n.commonConfirm,
|
||
);
|
||
if (mounted) context.pop();
|
||
}
|
||
|
||
/// 打开扫码页;扫描成功后把 SN 回填输入框。
|
||
Future<void> _openScanner(AppLocalizations l10n) async {
|
||
final code = await context.push<String>('/device/scan');
|
||
if (code == null || code.isEmpty || !mounted) return;
|
||
setState(() => _snController.text = code);
|
||
}
|
||
}
|