Files
ReelShort/lib/pages/account/account_deactivate_page.dart
T

212 lines
6.2 KiB
Dart

// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../core/data/repositories/account_repository.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/formatting/localized_date_time.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/auth_gate.dart';
import '../../core/widgets/plain_page_scaffold.dart';
import '../../l10n/app_localizations.dart';
import 'account_bindings.dart';
import 'account_deactivate_logic.dart';
/// 注销申请与冷静期撤销页。
class AccountDeactivatePage extends StatefulWidget {
const AccountDeactivatePage({super.key});
@override
State<AccountDeactivatePage> createState() => _AccountDeactivatePageState();
}
class _AccountDeactivatePageState extends State<AccountDeactivatePage> {
late final AccountDeactivateLogic _logic;
final _reason = TextEditingController();
@override
void initState() {
super.initState();
_logic = AccountDeactivateBinding().create(
context.read<AccountRepository>(),
context.read<AuthController>(),
);
}
@override
void dispose() {
_logic.dispose();
_reason.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final signedIn = context.watch<AuthController>().isSignedIn;
final canShow = signedIn || _logic.scheduledAt != null;
return Theme(
data: AppTheme.dark(),
child: PlainPageScaffold(
pageKey: const Key('account-deactivate-page'),
backKey: const Key('account-deactivate-back'),
title: l10n.accountDeactivate,
child: canShow
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
),
);
}
Widget _body(AppLocalizations l10n) {
return ListView(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 28),
children: [
_warning(l10n),
const SizedBox(height: 14),
if (_logic.scheduledAt != null)
_scheduledPanel(l10n)
else
_requestPanel(l10n),
const SizedBox(height: 14),
_cancelPanel(l10n),
],
);
}
Widget _warning(AppLocalizations l10n) {
final errorColor = Theme.of(context).colorScheme.error;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: errorColor.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: errorColor.withValues(alpha: 0.35)),
),
child: Text(
l10n.accountDeactivateWarning,
style: TextStyle(color: errorColor, height: 1.5),
),
);
}
Widget _scheduledPanel(AppLocalizations l10n) => Container(
key: const Key('account-deactivate-scheduled'),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
),
child: Text(
l10n.accountScheduledFor(
formatLocalizedDateTime(context, _logic.scheduledAt!),
),
),
);
Widget _requestPanel(AppLocalizations l10n) => Column(
children: [
TextField(
key: const Key('account-deactivate-reason'),
controller: _reason,
maxLength: AccountDeactivateLogic.maxReasonLength,
maxLines: 4,
decoration: InputDecoration(
labelText: l10n.accountDeactivateReason,
hintText: l10n.accountDeactivateReasonHint,
),
),
if (_logic.error != null)
_errorText(_message(l10n, _logic.error, l10n.accountDeactivateFailed)),
const SizedBox(height: 12),
FilledButton(
key: const Key('account-deactivate-submit'),
onPressed: _logic.requesting ? null : _confirm,
child: _logic.requesting
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(l10n.accountRequestDeactivate),
),
],
);
Widget _cancelPanel(AppLocalizations l10n) => Column(
children: [
OutlinedButton(
key: const Key('account-deactivate-cancel'),
onPressed: _logic.cancelling ? null : _cancel,
child: Text(l10n.accountCancelDeactivation),
),
if (_logic.actionMessage != null) ...[
const SizedBox(height: 8),
_errorText(
_message(l10n, _logic.actionMessage, l10n.accountCancelFailed),
),
],
],
);
Widget _errorText(String value) => Text(
value,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: AppDimens.caption,
),
);
Future<void> _confirm() async {
final l10n = AppLocalizations.of(context)!;
final confirmed = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(l10n.accountConfirmDeactivateTitle),
content: Text(l10n.accountConfirmDeactivateMessage),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: Text(l10n.cancel),
),
FilledButton(
key: const Key('account-deactivate-confirm'),
onPressed: () => Navigator.of(dialogContext).pop(true),
child: Text(l10n.accountConfirmDeactivate),
),
],
),
);
if (confirmed != true) return;
await _logic.request(_reason.text);
}
Future<void> _cancel() async {
final ok = await _logic.cancel();
if (!mounted) return;
final l10n = AppLocalizations.of(context)!;
if (ok) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(l10n.accountCancelSucceeded)));
}
}
String _message(AppLocalizations l10n, String? value, String fallback) {
if (value == null || value.isEmpty) return fallback;
return switch (value) {
'accountDeactivateFailed' => l10n.accountDeactivateFailed,
'accountCancelFailed' => l10n.accountCancelFailed,
_ => value,
};
}
}