184 lines
5.6 KiB
Dart
184 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:forui/forui.dart';
|
|
|
|
import '../../core/domain/errors.dart';
|
|
import '../../providers/di_providers.dart';
|
|
import '../constants/breakpoints.dart';
|
|
import '../utils/user_error_formatter.dart';
|
|
import 'adaptive_modal.dart';
|
|
import 'app_inline_alert.dart';
|
|
import 'app_loading_ring.dart';
|
|
import 'app_snack_bar.dart';
|
|
|
|
Future<void> showServerChangePasswordDialog({
|
|
required BuildContext context,
|
|
required String serverId,
|
|
required String serverName,
|
|
}) => showAdaptiveModal<void>(
|
|
context: context,
|
|
maxWidth: 420,
|
|
compactMaxHeightFactor: 0.9,
|
|
builder: (_) =>
|
|
_ServerChangePasswordDialog(serverId: serverId, serverName: serverName),
|
|
);
|
|
|
|
class _ServerChangePasswordDialog extends ConsumerStatefulWidget {
|
|
final String serverId;
|
|
final String serverName;
|
|
|
|
const _ServerChangePasswordDialog({
|
|
required this.serverId,
|
|
required this.serverName,
|
|
});
|
|
|
|
@override
|
|
ConsumerState<_ServerChangePasswordDialog> createState() =>
|
|
_ServerChangePasswordDialogState();
|
|
}
|
|
|
|
class _ServerChangePasswordDialogState
|
|
extends ConsumerState<_ServerChangePasswordDialog> {
|
|
final _currentPwCtrl = TextEditingController();
|
|
final _newPwCtrl = TextEditingController();
|
|
final _confirmPwCtrl = TextEditingController();
|
|
bool _loading = false;
|
|
String? _error;
|
|
|
|
@override
|
|
void dispose() {
|
|
_currentPwCtrl.dispose();
|
|
_newPwCtrl.dispose();
|
|
_confirmPwCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _submit() async {
|
|
final currentPw = _currentPwCtrl.text;
|
|
final newPw = _newPwCtrl.text;
|
|
final confirmPw = _confirmPwCtrl.text;
|
|
|
|
if (currentPw.isEmpty || newPw.isEmpty || confirmPw.isEmpty) {
|
|
setState(() => _error = '所有字段不能为空');
|
|
return;
|
|
}
|
|
if (newPw != confirmPw) {
|
|
setState(() => _error = '两次输入的新密码不一致');
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_loading = true;
|
|
_error = null;
|
|
});
|
|
|
|
try {
|
|
final uc = await ref.read(changePasswordUseCaseProvider.future);
|
|
await uc.execute(
|
|
serverId: widget.serverId,
|
|
currentPassword: currentPw,
|
|
newPassword: newPw,
|
|
);
|
|
if (mounted) Navigator.of(context).pop();
|
|
if (mounted) {
|
|
showAppSnackBar(context, '密码已修改', tone: AppSnackTone.neutral);
|
|
}
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_loading = false;
|
|
_error = e is DomainError ? formatUserError(e) : '修改失败,请检查当前密码是否正确';
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isCompact = Breakpoints.isCompact(MediaQuery.sizeOf(context).width);
|
|
final title = Text('修改密码 · ${widget.serverName}');
|
|
final body = SingleChildScrollView(
|
|
padding: EdgeInsets.fromLTRB(24, isCompact ? 16 : 24, 24, 24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
title,
|
|
const SizedBox(height: 18),
|
|
TextField(
|
|
controller: _currentPwCtrl,
|
|
obscureText: true,
|
|
enabled: !_loading,
|
|
textInputAction: TextInputAction.next,
|
|
autocorrect: false,
|
|
enableSuggestions: false,
|
|
decoration: const InputDecoration(labelText: '当前密码'),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _newPwCtrl,
|
|
obscureText: true,
|
|
enabled: !_loading,
|
|
textInputAction: TextInputAction.next,
|
|
autocorrect: false,
|
|
enableSuggestions: false,
|
|
decoration: const InputDecoration(labelText: '新密码'),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _confirmPwCtrl,
|
|
obscureText: true,
|
|
enabled: !_loading,
|
|
textInputAction: TextInputAction.done,
|
|
autocorrect: false,
|
|
enableSuggestions: false,
|
|
onSubmitted: (_) => _submit(),
|
|
decoration: const InputDecoration(labelText: '确认新密码'),
|
|
),
|
|
if (_error != null) ...[
|
|
const SizedBox(height: 12),
|
|
AppInlineAlert(message: _error!),
|
|
],
|
|
const SizedBox(height: 20),
|
|
if (isCompact)
|
|
FButton(
|
|
onPress: _loading ? null : _submit,
|
|
child: _loading
|
|
? const AppLoadingRing(size: 16, color: Colors.white)
|
|
: const Text('确认修改'),
|
|
)
|
|
else
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
FButton(
|
|
variant: FButtonVariant.ghost,
|
|
onPress: _loading
|
|
? null
|
|
: () => Navigator.of(context).pop(),
|
|
child: const Text('取消'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
FButton(
|
|
onPress: _loading ? null : _submit,
|
|
child: _loading
|
|
? const AppLoadingRing(size: 16, color: Colors.white)
|
|
: const Text('确认修改'),
|
|
),
|
|
],
|
|
),
|
|
if (isCompact) ...[
|
|
const SizedBox(height: 8),
|
|
FButton(
|
|
variant: FButtonVariant.outline,
|
|
onPress: _loading ? null : () => Navigator.of(context).pop(),
|
|
child: const Text('取消'),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
|
|
return PopScope(canPop: !_loading, child: body);
|
|
}
|
|
}
|