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

198 lines
5.7 KiB
Dart

// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../../core/data/models/account_export.dart';
import '../../core/data/repositories/account_repository.dart';
import '../../core/data/services/auth_controller.dart';
import '../../core/formatting/error_text.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/empty_state.dart';
import '../../l10n/app_localizations.dart';
import 'account_bindings.dart';
import 'account_export_logic.dart';
/// 账号数据导出摘要页。
class AccountExportPage extends StatefulWidget {
const AccountExportPage({super.key});
@override
State<AccountExportPage> createState() => _AccountExportPageState();
}
class _AccountExportPageState extends State<AccountExportPage> {
late final AccountExportLogic _logic;
@override
void initState() {
super.initState();
_logic = AccountExportBinding().create(
context.read<AccountRepository>(),
(text) => Clipboard.setData(ClipboardData(text: text)),
);
unawaited(_logic.load());
}
@override
void dispose() {
_logic.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final signedIn = context.watch<AuthController>().isSignedIn;
return Theme(
data: AppTheme.dark(),
child: Scaffold(
key: const Key('account-export-page'),
backgroundColor: AppColors.ink,
appBar: AppBar(
backgroundColor: AppColors.ink,
foregroundColor: AppColors.onPrimary,
title: Text(l10n.accountDataExport),
),
body: SafeArea(
top: false,
child: signedIn
? ListenableBuilder(
listenable: _logic,
builder: (context, _) => _body(l10n),
)
: const AuthGatePanel(),
),
),
);
}
Widget _body(AppLocalizations l10n) {
if (_logic.loading && _logic.data == null) {
return const Center(child: CircularProgressIndicator());
}
final data = _logic.data;
if (data == null) {
return EmptyState(
key: const Key('account-export-error'),
icon: Icons.cloud_off_rounded,
title: errorText(l10n, _logic.error, l10n.accountExportLoadFailed),
actionLabel: l10n.retry,
actionKey: const Key('account-export-retry'),
onAction: _logic.load,
);
}
return ListView(
key: const Key('account-export-content'),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 28),
children: [
_profileCard(data, l10n),
const SizedBox(height: 12),
_credentialsCard(data, l10n),
const SizedBox(height: 16),
_copyButton(l10n),
],
);
}
Widget _profileCard(AccountExport data, AppLocalizations l10n) => _card(
children: [
Text(
data.profile.nickname,
style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w700),
),
const SizedBox(height: 4),
Text(
'${l10n.accountUid}: ${data.profile.uid}',
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
],
);
Widget _credentialsCard(AccountExport data, AppLocalizations l10n) => _card(
children: [
Text(
l10n.accountCredentials,
style: const TextStyle(
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
),
),
for (final credential in data.credentials)
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(
Icons.verified_user_outlined,
color: AppColors.blueSoft,
),
title: Text(credential.masked),
subtitle: Text(credential.type),
trailing: Text(
credential.verified ? l10n.accountVerified : l10n.accountUnverified,
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
),
_summary(l10n.accountConsentRecords, '${data.consentCount}'),
_summary(l10n.accountDeviceCount, '${data.deviceCount}'),
],
);
Widget _copyButton(AppLocalizations l10n) => FilledButton.icon(
key: const Key('account-export-copy'),
onPressed: _copy,
icon: const Icon(Icons.copy_rounded),
label: Text(l10n.accountCopyJson),
);
Widget _card({required List<Widget> children}) => Container(
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
border: Border.all(color: AppColors.outlineVariantDark),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
);
Widget _summary(String label, String value) => Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
children: [
Expanded(
child: Text(
label,
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
),
),
),
Text(value, style: const TextStyle(fontWeight: FontWeight.w700)),
],
),
);
Future<void> _copy() async {
final ok = await _logic.copy();
if (!ok || !mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(AppLocalizations.of(context)!.accountCopied)),
);
}
}