import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/config/app_config.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/navigation/broker_navigation.dart'; import '../../../core/theme/app_colors.dart'; import '../../../providers/app_provider.dart'; import '../../../providers/app_version_provider.dart'; import '../../../providers/currency_provider.dart'; import '../../../providers/customer_service_provider.dart'; import '../../../providers/profile_provider.dart'; import '../../widgets/common/update_dialog.dart'; import '../../../core/utils/top_toast.dart'; class ProfileScreen extends ConsumerWidget { const ProfileScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; final state = ref.watch(profileProvider); final isLoggedIn = state.user.isLoggedIn; return Scaffold( appBar: AppBar( title: Text( l10n.profile, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), centerTitle: true, ), body: ListView( children: [ if (isLoggedIn) _UserCard(state: state) else const _GuestCard(), const SizedBox(height: 12), _SectionCard( title: l10n.quickFunctions, child: _QuickFunctions(l10n: l10n), ), const SizedBox(height: 12), _AppSettings(version: state.appVersion, isLoggedIn: isLoggedIn), if (isLoggedIn) ...[ const SizedBox(height: 24), _LogoutButton( onTap: () => _showLogoutDialog(context, ref), ), ], const SizedBox(height: 40), ], ), ); } void _showLogoutDialog(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: cs.surface, title: Text( l10n.logoutTitle, style: TextStyle(color: cs.onSurface), ), content: Text( l10n.logoutConfirm, style: TextStyle(color: cs.onSurface.withAlpha(153)), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: Text(l10n.cancel, style: TextStyle(color: cs.onSurface.withAlpha(153))), ), TextButton( onPressed: () { Navigator.of(ctx).pop(); ref.read(profileProvider.notifier).logout(); }, child: Text(l10n.confirm, style: const TextStyle(color: AppColors.brand)), ), ], ), ); } } // ── 未登录:访客卡片 ────────────────────────────────────────── class _GuestCard extends StatelessWidget { const _GuestCard(); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( margin: const EdgeInsets.fromLTRB(16, 16, 16, 0), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: cs.outline.withAlpha(40), shape: BoxShape.circle, ), child: Icon( Icons.person_outline, color: cs.onSurface.withAlpha(153), size: 32, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.guestGreeting, style: TextStyle( color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 10), SizedBox( height: 34, child: ElevatedButton( onPressed: () => context.push('/login'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.brand, foregroundColor: Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(17), ), elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 20), minimumSize: Size.zero, ), child: Text( l10n.loginRegister, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ], ), ); } } // ── 已登录:用户信息卡片 ───────────────────────────────────────── class _UserCard extends StatelessWidget { const _UserCard({required this.state}); final ProfileState state; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final user = state.user; final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( margin: const EdgeInsets.fromLTRB(16, 16, 16, 0), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Container( width: 56, height: 56, clipBehavior: Clip.antiAlias, decoration: const BoxDecoration( color: AppColors.brand, shape: BoxShape.circle, ), child: (user.avatarUrl != null && user.avatarUrl!.isNotEmpty) ? CachedNetworkImage( imageUrl: user.avatarUrl!, fit: BoxFit.cover, width: 56, height: 56, fadeInDuration: Duration.zero, errorWidget: (_, __, ___) => Center( child: Text( user.avatarLetter, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.w700, ), ), ), ) : Center( child: Text( user.avatarLetter, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.w700, ), ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user.email, style: TextStyle( color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), Row( children: [ Text( 'UID: ${user.uid}', style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13, ), ), const SizedBox(width: 6), GestureDetector( onTap: () { Clipboard.setData(ClipboardData(text: user.uid)); showTopToast(context, message: l10n.uidCopied, backgroundColor: AppColors.rise); }, child: Icon( Icons.copy, size: 14, color: cs.onSurface.withAlpha(153), ), ), ], ), ], ), ), ], ), ); } } // ── 卡片容器 ───────────────────────────────────────────────── class _SectionCard extends StatelessWidget { const _SectionCard({required this.title, required this.child}); final String title; final Widget child; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 14, 16, 10), child: Text( title, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13, ), ), ), child, ], ), ); } } // ── 常用功能 网格 ───────────────────────────────────────────── class _QuickFunctions extends StatelessWidget { final AppLocalizations l10n; const _QuickFunctions({required this.l10n}); @override Widget build(BuildContext context) { final items = [ ( icon: Icons.language, label: l10n.languageSwitch, route: '/user/language' ), ( icon: Icons.shield_outlined, label: l10n.security, route: '/user/security' ), ( icon: Icons.notifications_outlined, label: l10n.announcements, route: '/user/messages' ), (icon: Icons.help_outline, label: l10n.helpCenter, route: '/user/help'), ]; return Padding( padding: const EdgeInsets.fromLTRB(8, 0, 8, 16), child: Row( children: items .map((item) => Expanded( child: _QuickItemWidget( icon: item.icon, label: item.label, route: item.route))) .toList(), ), ); } } class _QuickItemWidget extends StatelessWidget { const _QuickItemWidget( {required this.icon, required this.label, required this.route}); final IconData icon; final String label; final String route; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return GestureDetector( onTap: () => context.push(route), child: Column( children: [ Container( width: 52, height: 52, decoration: BoxDecoration( color: cs.outline.withAlpha(30), borderRadius: BorderRadius.circular(14), ), child: Icon(icon, color: cs.onSurface, size: 24), ), const SizedBox(height: 6), Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 12, ), ), ], ), ); } } // ── APP 设置列表 ────────────────────────────────────────────── class _AppSettings extends ConsumerWidget { const _AppSettings({required this.version, required this.isLoggedIn}); final String version; final bool isLoggedIn; @override Widget build(BuildContext context, WidgetRef ref) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; final themeMode = ref.watch(themeProvider); final currencyState = ref.watch(currencyProvider); final selectedCurrency = currencyState.selected; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ if (isLoggedIn) ...[ _SettingRow( label: l10n.broker, onTap: () => openBrokerEntry(context, ref), showTopRadius: true, ), Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), ], _ThemeModeRow(themeMode: themeMode, ref: ref, l10n: l10n), Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), _SettingRow( label: l10n.currency, trailing: Text( selectedCurrency != null ? '${selectedCurrency.currency} ${selectedCurrency.symbol}' : 'USD \$', style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13), ), onTap: () => _showCurrencyPicker(context, ref, currencyState, l10n), ), Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), _SettingRow( label: l10n.stakingTitle, onTap: () => context.push('/finance/ido'), ), Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), _SettingRow( label: l10n.serviceRoute, onTap: () => context.push('/user/service-route'), showTopRadius: false, ), Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), _SettingRow( label: l10n.currentVersion, trailing: Text( version, style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13), ), onTap: () => _checkUpdate(context, ref, l10n), ), Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), _SettingRow( label: l10n.clearCache, onTap: () => _confirmClearCache(context, ref, l10n), showBottomRadius: !AppConfig.customerServiceEnabled, ), if (AppConfig.customerServiceEnabled) ...[ Column(children: [ Divider( height: 1, indent: 16, endIndent: 0, color: cs.outline.withAlpha(40)), _SettingRow( label: l10n.customerService, onTap: () => openCustomerService(context, ref), showBottomRadius: true, ), ]), ], ], ), ); } Future _checkUpdate( BuildContext context, WidgetRef ref, AppLocalizations l10n) async { ref.invalidate(appVersionProvider); final result = await ref.read(appVersionProvider.future); if (!context.mounted) return; if (result != null && result.hasUpdate) { UpdateDialog.show(context, result); } else { showTopToast(context, message: l10n.alreadyLatestVersion, backgroundColor: AppColors.rise); } } void _showCurrencyPicker(BuildContext context, WidgetRef ref, CurrencyState currencyState, AppLocalizations l10n) { final cs = Theme.of(context).colorScheme; showModalBottomSheet( context: context, useRootNavigator: true, isScrollControlled: true, backgroundColor: cs.surface, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (ctx) => Consumer( builder: (ctx, innerRef, _) { final live = innerRef.watch(currencyProvider); return SafeArea( child: ConstrainedBox( constraints: BoxConstraints( maxHeight: MediaQuery.of(ctx).size.height * 0.75, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 12), Container( width: 36, height: 4, decoration: BoxDecoration( color: cs.outline.withAlpha(80), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( l10n.selectCurrency, style: TextStyle( color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600), ), ), const SizedBox(height: 12), if (live.isLoading) Padding( padding: const EdgeInsets.symmetric(vertical: 24), child: CircularProgressIndicator( strokeWidth: 2, color: cs.onSurface), ) else if (live.rates.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 24), child: Text(l10n.noCurrencyAvailable, style: TextStyle(color: cs.onSurface.withAlpha(153))), ) else Flexible( child: SingleChildScrollView( child: Column( children: live.rates.map((rate) { final isSelected = rate.currency == live.selectedCode; return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { innerRef .read(currencyProvider.notifier) .selectCurrency(rate); Navigator.of(ctx).pop(); }, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14), child: Row( children: [ Text( rate.displayName, style: TextStyle( color: cs.onSurface, fontSize: 15, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, ), ), const Spacer(), if (isSelected) const Icon(Icons.check, color: AppColors.brand, size: 20), ], ), ), ); }).toList(), ), ), ), const SizedBox(height: 8), ], ), ), ); }, ), ); } Future _confirmClearCache( BuildContext context, WidgetRef ref, AppLocalizations l10n) async { final cs = Theme.of(context).colorScheme; final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: cs.surface, title: Text(l10n.tips, style: TextStyle(color: cs.onSurface)), content: Text( l10n.confirmClearCache, style: TextStyle(color: cs.onSurface.withAlpha(153)), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: Text(l10n.cancel, style: TextStyle(color: cs.onSurface.withAlpha(153))), ), TextButton( onPressed: () => Navigator.of(ctx).pop(true), child: Text(l10n.confirm, style: const TextStyle(color: AppColors.brand)), ), ], ), ); if (confirmed != true || !context.mounted) return; await ref.read(profileProvider.notifier).clearCache(); if (!context.mounted) return; showTopToast(context, message: l10n.cacheCleared, duration: const Duration(seconds: 1)); } } class _ThemeModeRow extends StatelessWidget { const _ThemeModeRow( {required this.themeMode, required this.ref, required this.l10n}); final ThemeMode themeMode; final WidgetRef ref; final AppLocalizations l10n; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( children: [ Text( l10n.themeColor, style: TextStyle(color: cs.onSurface, fontSize: 14), ), const SizedBox(width: 16), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Flexible( child: _ThemeChip( label: l10n.lightMode, selected: themeMode == ThemeMode.light, onTap: () => ref .read(themeProvider.notifier) .setTheme(ThemeMode.light), ), ), const SizedBox(width: 6), Flexible( child: _ThemeChip( label: l10n.darkMode, selected: themeMode == ThemeMode.dark, onTap: () => ref .read(themeProvider.notifier) .setTheme(ThemeMode.dark), ), ), ], ), ), ], ), ); } } class _ThemeChip extends StatelessWidget { const _ThemeChip( {required this.label, required this.selected, required this.onTap}); final String label; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: selected ? AppColors.brand : cs.outline.withAlpha(30), borderRadius: BorderRadius.circular(16), ), child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: selected ? Colors.black : cs.onSurface.withAlpha(153), fontSize: 12, fontWeight: selected ? FontWeight.w600 : FontWeight.normal, ), ), ), ); } } class _SettingRow extends StatelessWidget { const _SettingRow({ required this.label, required this.onTap, this.trailing, this.showTopRadius = false, this.showBottomRadius = false, }); final String label; final VoidCallback onTap; final Widget? trailing; final bool showTopRadius; final bool showBottomRadius; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return InkWell( onTap: onTap, borderRadius: BorderRadius.vertical( top: showTopRadius ? const Radius.circular(12) : Radius.zero, bottom: showBottomRadius ? const Radius.circular(12) : Radius.zero, ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ Expanded( child: Text( label, style: TextStyle( color: cs.onSurface, fontSize: 14, ), ), ), if (trailing != null) trailing!, const SizedBox(width: 4), Icon(Icons.chevron_right, size: 18, color: cs.onSurface.withAlpha(102)), ], ), ), ); } } // ── 退出登录按钮 ────────────────────────────────────────────── class _LogoutButton extends StatelessWidget { const _LogoutButton({required this.onTap}); final VoidCallback onTap; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: GestureDetector( onTap: onTap, child: Container( height: 48, decoration: BoxDecoration( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary, borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( l10n.logoutButton, style: TextStyle( color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w500, ), ), ), ), ), ); } }