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'; /// 密码修改面板 /// /// 在设置页右侧主区域渲染密码修改表单,行为与原弹窗一致: /// 校验空值、长度(≥6)、两次一致性、原密码正确性。 class PasswordPanel extends ConsumerStatefulWidget { const PasswordPanel({super.key}); @override ConsumerState createState() => _PasswordPanelState(); } class _PasswordPanelState extends ConsumerState { final _oldCtrl = TextEditingController(); final _newCtrl = TextEditingController(); final _confirmCtrl = TextEditingController(); bool _submitting = false; String? _error; @override void dispose() { _oldCtrl.dispose(); _newCtrl.dispose(); _confirmCtrl.dispose(); super.dispose(); } Future _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 = l10n?.fillAllFields ?? '请填写所有字段'); return; } if (newPwd.length < 6) { setState(() => _error = l10n?.newPwdMinLength ?? '新密码至少6位字符'); return; } if (newPwd != confirmPwd) { setState(() => _error = l10n?.passwordMismatch ?? '两次输入的新密码不一致'); return; } setState(() { _submitting = true; _error = null; }); final isValid = await SettingsService.instance.verifyPassword(oldPwd); if (!isValid) { if (!mounted) return; setState(() { _submitting = false; _error = l10n?.oldPasswordError ?? '原密码错误'; }); return; } final ok = await SettingsService.instance.setPassword(newPwd); if (!mounted) return; setState(() => _submitting = false); if (ok) { _oldCtrl.clear(); _newCtrl.clear(); _confirmCtrl.clear(); ToastService.showSuccess(context, l10n?.passwordChanged ?? '密码已修改'); } else { ToastService.showError(context, l10n?.passwordChangeFailed ?? '密码修改失败'); } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return ListView( padding: EdgeInsets.zero, children: [ Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: AppTheme.borderSubtle), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( l10n?.password ?? '密码修改', style: TextStyle( color: AppTheme.textPrimary, fontSize: 16, fontWeight: FontWeight.w600, ), ), const Divider(), _field( controller: _oldCtrl, label: l10n?.oldPassword ?? '原密码', hint: l10n?.enterCurrentPassword ?? '请输入当前密码', ), const SizedBox(height: 16), _field( controller: _newCtrl, label: l10n?.newPassword ?? '新密码', hint: l10n?.passwordMinLength ?? '至少6位字符', ), const SizedBox(height: 16), _field( controller: _confirmCtrl, label: l10n?.confirmNewPassword ?? '确认新密码', hint: l10n?.enterNewPassword ?? '再次输入新密码', ), if (_error != null) ...[ const SizedBox(height: 12), Text( _error!, style: TextStyle(color: AppTheme.errorColor, fontSize: 12), ), ], const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ OutlinedButton( onPressed: _submitting ? null : () { _oldCtrl.clear(); _newCtrl.clear(); _confirmCtrl.clear(); setState(() => _error = null); }, child: Text(l10n?.reset ?? '重置'), ), const SizedBox(width: 12), ElevatedButton.icon( onPressed: _submitting ? null : _submit, icon: _submitting ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Icon(Icons.check, size: 18), label: Text(l10n?.confirm ?? '确认'), ), ], ), ], ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Text( l10n?.defaultPassword ?? '默认密码为 123456', style: TextStyle(color: AppTheme.textSecondary, fontSize: 12), ), ), ], ); } Widget _field({ required TextEditingController controller, required String label, required String hint, }) { return TextField( controller: controller, obscureText: true, decoration: InputDecoration( labelText: label, hintText: hint, border: const OutlineInputBorder(), isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), ); } }