import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:provider/provider.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/utils/toast_utils.dart'; import '../../../data/models/account_models.dart'; import '../../../data/services/asset_service.dart'; import '../../../data/services/bonus_service.dart'; import '../../../providers/asset_provider.dart'; import '../../../providers/auth_provider.dart'; import '../../components/glass_panel.dart'; import '../../components/neon_glow.dart'; import '../main/main_page.dart'; /// 首页 class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State with AutomaticKeepAliveClientMixin { bool _bonusClaimed = false; bool _bonusLoading = false; @override bool get wantKeepAlive => true; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } void _loadData() { final provider = context.read(); provider.loadOverview(); provider.loadTradeAccount(); _checkBonusStatus(); } Future _checkBonusStatus() async { try { final bonusService = context.read(); final response = await bonusService.getStatus(); if (response.success && response.data != null) { setState(() { _bonusClaimed = response.data!['claimed'] == true; }); } } catch (_) {} } @override Widget build(BuildContext context) { super.build(context); final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.background, body: Consumer( builder: (context, provider, _) { return RefreshIndicator( onRefresh: () => provider.refreshAll(force: true), color: colorScheme.primary, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( left: AppSpacing.lg, right: AppSpacing.lg, top: AppSpacing.sm, bottom: 100, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 问候 _GreetingSection(), SizedBox(height: AppSpacing.md), // 资产卡片(含总盈利 + 可折叠盈亏日历) _AssetCard( overview: provider.overview, onDeposit: _showDeposit, ), SizedBox(height: AppSpacing.md), // 新人福利卡片 _BonusCard( isClaimed: _bonusClaimed, onClaim: _claimBonus, isLoading: _bonusLoading, ), SizedBox(height: AppSpacing.lg), // 持仓 _HoldingsSection(holdings: provider.holdings), ], ), ), ); }, ), ); } void _showDeposit() { final amountController = TextEditingController(); final formKey = GlobalKey(); final colorScheme = Theme.of(context).colorScheme; showShadDialog( context: context, builder: (ctx) => Dialog( backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.xxl), padding: EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '充值', style: GoogleFonts.spaceGrotesk( fontSize: 24, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), SizedBox(height: AppSpacing.xs), Text( '资产: USDT', style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant, ), ), ], ), Container( padding: EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( color: colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.md), ), child: Icon(LucideIcons.wallet, color: colorScheme.secondary), ), ], ), SizedBox(height: AppSpacing.lg), ShadForm( key: formKey, child: ShadInputFormField( id: 'amount', controller: amountController, label: const Text('充值金额'), placeholder: const Text('0.00'), keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: (v) { if (v == null || v.isEmpty) return '请输入金额'; final n = double.tryParse(v); if (n == null || n <= 0) return '请输入有效金额'; return null; }, ), ), SizedBox(height: AppSpacing.lg), Row( children: [ Expanded( child: NeonButton( text: '取消', type: NeonButtonType.outline, onPressed: () => Navigator.of(ctx).pop(), height: 48, showGlow: false, ), ), SizedBox(width: AppSpacing.sm), Expanded( child: NeonButton( text: '下一步', type: NeonButtonType.primary, onPressed: () async { if (formKey.currentState!.saveAndValidate()) { Navigator.of(ctx).pop(); final response = await context .read() .deposit(amount: amountController.text); if (mounted) { if (response.success && response.data != null) { _showDepositResultDialog(context, response.data!); } else { _showResultDialog( '申请失败', response.message ?? '请稍后重试', ); } } } }, height: 48, showGlow: true, ), ), ], ), ], ), ), ), ); } void _showDepositResultDialog(BuildContext context, Map data) { final orderNo = data['orderNo'] as String? ?? ''; final amount = data['amount']?.toString() ?? '0.00'; final walletAddress = data['walletAddress'] as String? ?? ''; final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20'; final colorScheme = Theme.of(context).colorScheme; showShadDialog( context: context, builder: (ctx) => Dialog( backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.xxl), padding: EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ NeonIcon( icon: Icons.check_circle, color: AppColorScheme.up, size: 24, ), SizedBox(width: AppSpacing.sm), Text( '充值申请成功', style: GoogleFonts.spaceGrotesk( fontSize: 20, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), ], ), SizedBox(height: AppSpacing.lg), _InfoRow(label: '订单号', value: orderNo), SizedBox(height: AppSpacing.sm), _InfoRow(label: '充值金额', value: '$amount USDT', isBold: true), SizedBox(height: AppSpacing.lg), Text( '请向以下地址转账:', style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant), ), SizedBox(height: AppSpacing.sm), _WalletAddressCard(address: walletAddress, network: walletNetwork), SizedBox(height: AppSpacing.md), Container( padding: EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( color: AppColorScheme.warning.withOpacity(0.1), borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all(color: AppColorScheme.warning.withOpacity(0.2)), ), child: Row( children: [ Icon(Icons.info_outline, size: 16, color: AppColorScheme.warning), SizedBox(width: AppSpacing.sm), Expanded( child: Text( '转账完成后请点击"已打款"按钮确认', style: TextStyle(fontSize: 12, color: AppColorScheme.warning), ), ), ], ), ), SizedBox(height: AppSpacing.lg), Row( children: [ Expanded( child: NeonButton( text: '稍后确认', type: NeonButtonType.outline, onPressed: () { Navigator.of(ctx).pop(); _navigateToAssetPage(); }, height: 44, showGlow: false, ), ), SizedBox(width: AppSpacing.sm), Expanded( child: NeonButton( text: '已打款', type: NeonButtonType.primary, onPressed: () async { Navigator.of(ctx).pop(); final response = await context .read() .confirmPay(orderNo); if (context.mounted) { _showResultDialog( response.success ? '确认成功' : '确认失败', response.success ? '请等待管理员审核' : response.message, ); _navigateToAssetPage(); } }, height: 44, showGlow: true, ), ), ], ), ], ), ), ), ); } void _showResultDialog(String title, String? message) { final colorScheme = Theme.of(context).colorScheme; showShadDialog( context: context, builder: (ctx) => Dialog( backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.xxl), padding: EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(title, style: GoogleFonts.spaceGrotesk( fontSize: 20, fontWeight: FontWeight.bold, color: colorScheme.onSurface, )), if (message != null) ...[ SizedBox(height: AppSpacing.sm), Text(message, style: TextStyle(color: colorScheme.onSurfaceVariant), textAlign: TextAlign.center), ], SizedBox(height: AppSpacing.lg), SizedBox( width: double.infinity, child: NeonButton( text: '确定', type: NeonButtonType.primary, onPressed: () => Navigator.of(ctx).pop(), height: 44, showGlow: false, ), ), ], ), ), ), ); } /// 跳转到资产页面 void _navigateToAssetPage() { final mainState = context.findAncestorStateOfType(); mainState?.switchToTab(3); } Future _claimBonus() async { setState(() => _bonusLoading = true); try { final bonusService = context.read(); final response = await bonusService.claim(); if (!mounted) return; if (response.success) { setState(() => _bonusClaimed = true); context.read().refreshAll(force: true); showShadDialog( context: context, builder: (ctx) => ShadDialog.alert( title: const Text('领取成功'), description: Text('50 USDT 已到账,请划转至交易账户后即可开始交易'), actions: [ ShadButton( child: const Text('确定'), onPressed: () => Navigator.of(ctx).pop(), ), ], ), ); } else { showShadDialog( context: context, builder: (ctx) => ShadDialog.alert( title: const Text('领取失败'), description: Text(response.message ?? '请稍后重试'), actions: [ ShadButton( child: const Text('确定'), onPressed: () => Navigator.of(ctx).pop(), ), ], ), ); } } catch (e) { if (mounted) { showShadDialog( context: context, builder: (ctx) => ShadDialog.alert( title: const Text('领取失败'), description: Text(e.toString()), actions: [ ShadButton( child: const Text('确定'), onPressed: () => Navigator.of(ctx).pop(), ), ], ), ); } } finally { if (mounted) setState(() => _bonusLoading = false); } } } /// 问候区域 class _GreetingSection extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Consumer( builder: (context, auth, _) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '欢迎回来,', style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 14, ), ), SizedBox(height: AppSpacing.xs), Text( auth.user?.username ?? '用户', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 24, ), ), ], ); }, ); } } /// 资产卡片(含总盈利 + 可折叠盈亏日历) class _AssetCard extends StatefulWidget { final AssetOverview? overview; final VoidCallback onDeposit; const _AssetCard({required this.overview, required this.onDeposit}); @override State<_AssetCard> createState() => _AssetCardState(); } class _AssetCardState extends State<_AssetCard> { bool _calendarExpanded = false; late DateTime _currentMonth; Map? _profitData; bool _isLoadingCalendar = false; @override void initState() { super.initState(); _currentMonth = DateTime.now(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadProfit()); } double get _totalProfit { final v = widget.overview?.totalProfit; if (v == null) return 0; return double.tryParse(v) ?? 0; } Future _toggleCalendar() async { setState(() => _calendarExpanded = !_calendarExpanded); if (_calendarExpanded && _profitData == null) { await _loadProfit(); } } Future _loadProfit() async { setState(() => _isLoadingCalendar = true); try { final assetService = context.read(); final response = await assetService.getDailyProfit( year: _currentMonth.year, month: _currentMonth.month, ); if (mounted) { setState(() { _profitData = response.data; _isLoadingCalendar = false; }); } } catch (_) { if (mounted) setState(() => _isLoadingCalendar = false); } } void _previousMonth() { setState(() { _currentMonth = DateTime(_currentMonth.year, _currentMonth.month - 1); }); _loadProfit(); } void _nextMonth() { setState(() { _currentMonth = DateTime(_currentMonth.year, _currentMonth.month + 1); }); _loadProfit(); } double? _getDayProfit(int day) { if (_profitData == null) return null; final daily = _profitData!['daily'] as Map?; if (daily == null) return null; final dateStr = '${_currentMonth.year}-${_currentMonth.month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}'; final value = daily[dateStr]; if (value == null) return null; return (value is num) ? value.toDouble() : double.tryParse(value.toString()); } double get _monthProfit { if (_profitData == null) return 0; final value = _profitData!['totalProfit']; if (value == null) return 0; return (value is num) ? value.toDouble() : double.tryParse(value.toString()) ?? 0; } double? get _todayProfit { if (_profitData == null) return null; final daily = _profitData!['daily'] as Map?; if (daily == null) return null; final now = DateTime.now(); final todayKey = '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}'; final value = daily[todayKey]; if (value == null) return null; return (value is num) ? value.toDouble() : double.tryParse(value.toString()); } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; final upColor = AppColorScheme.getUpColor(isDark); final downColor = AppColorScheme.down; final isProfit = _totalProfit >= 0; final todayProfit = _todayProfit; final isTodayProfit = (todayProfit ?? 0) >= 0; // 总资产 final totalAsset = widget.overview?.totalAsset ?? '0.00'; final displayAsset = _formatAsset(totalAsset); return GlassPanel( padding: EdgeInsets.all(AppSpacing.lg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 顶部行:总资产标签 + 充值按钮 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '预估总资产(USDT)', style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 12, ), ), GestureDetector( onTap: widget.onDeposit, child: Container( padding: EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.xs + 2, ), decoration: BoxDecoration( color: colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(AppRadius.full), border: Border.all(color: colorScheme.primary.withOpacity(0.2)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add, size: 14, color: colorScheme.primary), SizedBox(width: 4), Text( '充值', style: TextStyle( color: colorScheme.primary, fontSize: 12, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ), SizedBox(height: AppSpacing.sm), // 总资产金额 Text( displayAsset, style: GoogleFonts.spaceGrotesk( fontSize: 32, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), SizedBox(height: AppSpacing.md), // 盈亏统计区:今日盈亏 | 总盈亏 + 盈亏分析按钮 Row( children: [ // 今日盈亏卡片 Expanded( child: _ProfitStatCard( label: '今日盈亏', value: _todayProfit, upColor: upColor, downColor: downColor, ), ), SizedBox(width: AppSpacing.sm), // 总盈亏卡片 Expanded( child: _ProfitStatCard( label: '总盈亏', value: _totalProfit, upColor: upColor, downColor: downColor, ), ), SizedBox(width: AppSpacing.sm), // 盈亏分析按钮 GestureDetector( onTap: _toggleCalendar, child: Container( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: _calendarExpanded ? colorScheme.primary.withOpacity(0.1) : colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.full), border: _calendarExpanded ? Border.all(color: colorScheme.primary.withOpacity(0.2)) : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.chartBar, size: 13, color: _calendarExpanded ? colorScheme.primary : colorScheme.onSurfaceVariant, ), SizedBox(width: 4), Text( '盈亏分析', style: TextStyle( fontSize: 11, fontWeight: _calendarExpanded ? FontWeight.w600 : FontWeight.w500, color: _calendarExpanded ? colorScheme.primary : colorScheme.onSurfaceVariant, ), ), SizedBox(width: 2), AnimatedRotation( turns: _calendarExpanded ? 0.5 : 0, duration: const Duration(milliseconds: 200), child: Icon( LucideIcons.chevronDown, size: 13, color: _calendarExpanded ? colorScheme.primary : colorScheme.onSurfaceVariant, ), ), ], ), ), ), ], ), // 可折叠的盈利日历 AnimatedCrossFade( firstChild: const SizedBox.shrink(), secondChild: _buildCalendarSection( context, colorScheme, isDark, upColor, downColor, ), crossFadeState: _calendarExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: const Duration(milliseconds: 300), sizeCurve: Curves.easeInOut, ), ], ), ); } Widget _buildCalendarSection( BuildContext context, ColorScheme colorScheme, bool isDark, Color upColor, Color downColor, ) { final now = DateTime.now(); final isCurrentMonth = _currentMonth.year == now.year && _currentMonth.month == now.month; final firstDayOfMonth = DateTime(_currentMonth.year, _currentMonth.month, 1); final daysInMonth = DateTime(_currentMonth.year, _currentMonth.month + 1, 0).day; final startWeekday = firstDayOfMonth.weekday; return Column( mainAxisSize: MainAxisSize.min, children: [ // 分隔线 Padding( padding: EdgeInsets.symmetric(vertical: AppSpacing.md), child: Divider(color: colorScheme.outlineVariant.withOpacity(0.15), height: 1), ), // 月份导航行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: _previousMonth, child: Container( padding: EdgeInsets.all(AppSpacing.xs + 1), decoration: BoxDecoration( color: colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Icon(LucideIcons.chevronLeft, size: 16, color: colorScheme.onSurfaceVariant), ), ), Column( children: [ Text( '${_currentMonth.year}年${_currentMonth.month}月', style: GoogleFonts.spaceGrotesk( fontSize: 14, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), SizedBox(height: 2), if (!_isLoadingCalendar && _profitData != null) Text( '月度盈亏: ${_monthProfit >= 0 ? '+' : ''}${_monthProfit.toStringAsFixed(2)}', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: _monthProfit >= 0 ? upColor : downColor, ), ), ], ), GestureDetector( onTap: isCurrentMonth ? null : _nextMonth, child: Container( padding: EdgeInsets.all(AppSpacing.xs + 1), decoration: BoxDecoration( color: isCurrentMonth ? colorScheme.surfaceContainerHigh.withOpacity(0.5) : colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Icon( LucideIcons.chevronRight, size: 16, color: isCurrentMonth ? colorScheme.onSurfaceVariant.withOpacity(0.4) : colorScheme.onSurfaceVariant, ), ), ), ], ), SizedBox(height: AppSpacing.sm), // 星期标题 Row( children: ['一', '二', '三', '四', '五', '六', '日'].map((d) { return Expanded( child: Center( child: Text( d, style: TextStyle( fontSize: 10, color: colorScheme.onSurfaceVariant.withOpacity(0.6), fontWeight: FontWeight.w500, ), ), ), ); }).toList(), ), SizedBox(height: AppSpacing.xs), // 日历网格 if (_isLoadingCalendar) Padding( padding: EdgeInsets.symmetric(vertical: AppSpacing.lg), child: SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: colorScheme.primary, ), ), ) else ..._buildCalendarGrid( startWeekday, daysInMonth, now, isCurrentMonth, upColor, downColor, colorScheme, ), ], ); } List _buildCalendarGrid( int startWeekday, int daysInMonth, DateTime now, bool isCurrentMonth, Color upColor, Color downColor, ColorScheme colorScheme, ) { final List rows = []; List cells = []; for (int i = 1; i < startWeekday; i++) { cells.add(const Expanded(child: SizedBox.shrink())); } for (int day = 1; day <= daysInMonth; day++) { final profit = _getDayProfit(day); final isToday = isCurrentMonth && day == now.day; final hasProfit = profit != null && profit != 0; cells.add( Expanded( child: AspectRatio( aspectRatio: 1, child: Container( margin: EdgeInsets.all(1), decoration: BoxDecoration( color: isToday ? colorScheme.primary.withOpacity(0.12) : hasProfit ? (profit! > 0 ? upColor.withOpacity(0.08) : downColor.withOpacity(0.08)) : Colors.transparent, borderRadius: BorderRadius.circular(AppRadius.sm), border: isToday ? Border.all(color: colorScheme.primary.withOpacity(0.4), width: 1) : null, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '$day', style: TextStyle( fontSize: 10, fontWeight: isToday ? FontWeight.bold : FontWeight.w400, color: isToday ? colorScheme.primary : colorScheme.onSurface, ), ), if (hasProfit) ...[ SizedBox(height: 1), Text( '${profit! > 0 ? '+' : ''}${profit.abs() < 10 ? profit.toStringAsFixed(2) : profit.toStringAsFixed(1)}', style: TextStyle( fontSize: 7, fontWeight: FontWeight.w600, color: profit > 0 ? upColor : downColor, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ], ), ), ), ), ); if (cells.length == 7) { rows.add(Row(children: cells)); cells = []; } } if (cells.isNotEmpty) { while (cells.length < 7) { cells.add(const Expanded(child: SizedBox.shrink())); } rows.add(Row(children: cells)); } return rows; } String _formatAsset(String value) { final d = double.tryParse(value) ?? 0.0; return d.toStringAsFixed(2); } } /// 新人福利卡片 class _BonusCard extends StatelessWidget { final bool isClaimed; final VoidCallback onClaim; final bool isLoading; const _BonusCard({required this.isClaimed, required this.onClaim, required this.isLoading}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Opacity( opacity: isClaimed ? 0.6 : 1.0, child: GestureDetector( onTap: isClaimed || isLoading ? null : onClaim, child: Container( width: double.infinity, padding: EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( gradient: LinearGradient( colors: [ colorScheme.primary.withOpacity(0.15), colorScheme.secondary.withOpacity(0.1), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(AppRadius.xl), border: Border.all(color: colorScheme.primary.withOpacity(0.2)), ), child: Row( children: [ // 左侧图标 Container( width: 48, height: 48, decoration: BoxDecoration( color: isClaimed ? colorScheme.onSurfaceVariant.withOpacity(0.1) : colorScheme.primary.withOpacity(0.15), borderRadius: BorderRadius.circular(AppRadius.lg), ), child: Icon( isClaimed ? LucideIcons.check : LucideIcons.gift, color: isClaimed ? AppColorScheme.up : colorScheme.primary, size: 24, ), ), SizedBox(width: AppSpacing.md), // 中间文字 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '新人福利', style: GoogleFonts.spaceGrotesk( fontSize: 16, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), SizedBox(height: AppSpacing.xs), Text( isClaimed ? '50 USDT 体验金已到账' : '领取 50 USDT 体验金,开始您的交易之旅', style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant, ), ), ], ), ), // 右侧按钮 Container( padding: EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.sm, ), decoration: BoxDecoration( color: isClaimed ? colorScheme.onSurfaceVariant.withOpacity(0.15) : null, gradient: isClaimed ? null : (isDark ? AppColorScheme.darkCtaGradient : AppColorScheme.lightCtaGradient), borderRadius: BorderRadius.circular(AppRadius.full), ), child: isLoading ? SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: isDark ? colorScheme.background : Colors.white, ), ) : Text( isClaimed ? '已领取' : '立即领取', style: TextStyle( color: isClaimed ? colorScheme.onSurfaceVariant : (isDark ? colorScheme.background : Colors.white), fontSize: 13, fontWeight: FontWeight.w700, ), ), ), ], ), ), ), ); } } /// 持仓部分 class _HoldingsSection extends StatelessWidget { final List holdings; const _HoldingsSection({required this.holdings}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '我的持仓', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 18, ), ), TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: colorScheme.primary, padding: const EdgeInsets.symmetric(horizontal: 8), ), child: Row( children: [ Text('资产详情', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14)), const SizedBox(width: 4), Icon(LucideIcons.chevronRight, size: 16, color: colorScheme.primary), ], ), ), ], ), SizedBox(height: AppSpacing.md), holdings.isEmpty ? const _EmptyHoldings() : _HoldingsList(holdings: holdings), ], ); } } /// 空持仓 class _EmptyHoldings extends StatelessWidget { const _EmptyHoldings(); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container( width: double.infinity, padding: EdgeInsets.symmetric(vertical: AppSpacing.xxl, horizontal: AppSpacing.lg), decoration: BoxDecoration( color: colorScheme.surfaceContainerLow.withOpacity(0.5), borderRadius: BorderRadius.circular(AppRadius.xxl), border: Border.all(color: colorScheme.outlineVariant.withOpacity(0.1)), ), child: Column( children: [ Icon(LucideIcons.wallet, size: 48, color: colorScheme.onSurfaceVariant), SizedBox(height: AppSpacing.md), Text( '暂无持仓', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.w600, fontSize: 16, ), ), SizedBox(height: AppSpacing.sm), Text( '快去交易吧~', style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 14, ), ), ], ), ); } } /// 持仓列表 class _HoldingsList extends StatelessWidget { final List holdings; const _HoldingsList({required this.holdings}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final displayHoldings = holdings.length > 5 ? holdings.sublist(0, 5) : holdings; return Container( decoration: BoxDecoration( color: colorScheme.surface.withOpacity(0.5), borderRadius: BorderRadius.circular(AppRadius.xxl), border: Border.all(color: colorScheme.outlineVariant.withOpacity(0.1)), ), child: ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.all(AppSpacing.md), itemCount: displayHoldings.length, separatorBuilder: (_, __) => Divider( color: colorScheme.outlineVariant.withOpacity(0.1), height: 1, ), itemBuilder: (context, index) => _HoldingItem(holding: displayHoldings[index]), ), ); } } /// 持仓项 class _HoldingItem extends StatelessWidget { final AccountTrade holding; const _HoldingItem({required this.holding}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Padding( padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ CircleAvatar( radius: 18, backgroundColor: colorScheme.primary.withOpacity(0.1), child: Text( holding.coinCode.substring(0, 1), style: TextStyle( color: colorScheme.primary, fontWeight: FontWeight.bold, ), ), ), SizedBox(width: AppSpacing.sm + AppSpacing.xs), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(holding.coinCode, style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 16, )), Text(holding.quantity, style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 12, )), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('${holding.currentValue} USDT', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.w500, fontSize: 14, )), Text(holding.formattedProfitRate, style: TextStyle( color: holding.isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down, fontSize: 12, fontWeight: FontWeight.w500, )), ], ), ], ), ); } } /// 信息行组件 class _InfoRow extends StatelessWidget { final String label; final String value; final bool isBold; const _InfoRow({required this.label, required this.value, this.isBold = false}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant, )), Text(value, style: GoogleFonts.spaceGrotesk( fontSize: 12, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: colorScheme.onSurface, )), ], ); } } /// 钱包地址卡片 class _WalletAddressCard extends StatelessWidget { final String address; final String network; const _WalletAddressCard({required this.address, required this.network}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container( padding: EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all(color: colorScheme.outlineVariant.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( address, style: TextStyle( fontFamily: 'monospace', fontSize: 12, color: colorScheme.onSurface, ), ), ), GestureDetector( onTap: () { Clipboard.setData(ClipboardData(text: address)); ToastUtils.show('地址已复制到剪贴板'); }, child: Container( padding: EdgeInsets.all(AppSpacing.xs), decoration: BoxDecoration( color: colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Icon(LucideIcons.copy, size: 16, color: colorScheme.primary), ), ), ], ), SizedBox(height: AppSpacing.sm), Text( '网络: $network', style: TextStyle( fontSize: 11, color: colorScheme.onSurfaceVariant, ), ), ], ), ); } } /// 盈亏统计小卡片 class _ProfitStatCard extends StatelessWidget { final String label; final double? value; final Color upColor; final Color downColor; const _ProfitStatCard({ required this.label, required this.value, required this.upColor, required this.downColor, }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final hasValue = value != null; final isProfit = (value ?? 0) >= 0; final color = hasValue ? (isProfit ? upColor : downColor) : colorScheme.onSurfaceVariant; return Container( padding: EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: AppSpacing.sm + 2), decoration: BoxDecoration( color: hasValue ? (isProfit ? upColor : downColor).withOpacity(0.06) : colorScheme.surfaceContainerHigh.withOpacity(0.5), borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all( color: hasValue ? (isProfit ? upColor : downColor).withOpacity(0.12) : colorScheme.outlineVariant.withOpacity(0.1), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( hasValue ? (isProfit ? LucideIcons.trendingUp : LucideIcons.trendingDown) : LucideIcons.minus, size: 11, color: color.withOpacity(0.7), ), SizedBox(width: 3), Text( label, style: TextStyle( fontSize: 10, color: color.withOpacity(0.8), fontWeight: FontWeight.w500, ), ), ], ), SizedBox(height: AppSpacing.xs), Text( hasValue ? '${isProfit ? '+' : ''}${value!.toStringAsFixed(2)}' : '--', style: GoogleFonts.spaceGrotesk( fontSize: 15, fontWeight: FontWeight.bold, color: color, ), ), ], ), ); } }