// 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 createState() => _AccountDeactivatePageState(); } class _AccountDeactivatePageState extends State { late final AccountDeactivateLogic _logic; final _reason = TextEditingController(); @override void initState() { super.initState(); _logic = AccountDeactivateBinding().create( context.read(), context.read(), ); } @override void dispose() { _logic.dispose(); _reason.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final signedIn = context.watch().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 _confirm() async { final l10n = AppLocalizations.of(context)!; final confirmed = await showDialog( 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 _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, }; } }