198 lines
5.7 KiB
Dart
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.light(),
|
|
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)),
|
|
);
|
|
}
|
|
}
|