218 lines
6.3 KiB
Dart
218 lines
6.3 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 '../../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.light(),
|
|
child: Scaffold(
|
|
key: const Key('account-deactivate-page'),
|
|
backgroundColor: AppColors.ink,
|
|
appBar: AppBar(
|
|
backgroundColor: AppColors.ink,
|
|
foregroundColor: AppColors.onPrimary,
|
|
title: Text(l10n.accountDeactivate),
|
|
),
|
|
body: SafeArea(
|
|
top: false,
|
|
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,
|
|
};
|
|
}
|
|
}
|