// 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 '../../core/widgets/plain_page_scaffold.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 createState() => _AccountExportPageState(); } class _AccountExportPageState extends State { late final AccountExportLogic _logic; @override void initState() { super.initState(); _logic = AccountExportBinding().create( context.read(), (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().isSignedIn; return Theme( data: AppTheme.dark(), child: PlainPageScaffold( pageKey: const Key('account-export-page'), backKey: const Key('account-export-back'), title: l10n.accountDataExport, 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 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 _copy() async { final ok = await _logic.copy(); if (!ok || !mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(AppLocalizations.of(context)!.accountCopied)), ); } }