import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/router/routes.dart'; import '../../../core/utils/format_utils.dart'; import '../../../domain/entities/asset_display.dart'; import '../../providers/user_providers.dart'; import '../../providers/asset_providers.dart'; import '../../providers/mining_providers.dart'; import '../../widgets/shimmer_loading.dart'; class AssetPage extends ConsumerStatefulWidget { const AssetPage({super.key}); @override ConsumerState createState() => _AssetPageState(); } class _AssetPageState extends ConsumerState { // 设计色彩 static const Color _orange = Color(0xFFFF6B00); static const Color _green = Color(0xFF10B981); static const Color _grayText = Color(0xFF6B7280); static const Color _darkText = Color(0xFF1F2937); static const Color _bgGray = Color(0xFFF3F4F6); static const Color _riverBed = Color(0xFF4B5563); static const Color _serenade = Color(0xFFFFF7ED); static const Color _feta = Color(0xFFF0FDF4); static const Color _scandal = Color(0xFFDCFCE7); static const Color _jewel = Color(0xFF15803D); // 实时刷新相关状态 Timer? _refreshTimer; int _elapsedSeconds = 0; double _initialShareBalance = 0; double _growthPerSecond = 0; String? _lastAccountSequence; bool _timerStarted = false; @override void dispose() { _refreshTimer?.cancel(); super.dispose(); } /// 启动定时器(使用外部传入的每秒增长值) void _startTimerWithGrowth(AssetDisplay asset, String perSecondEarning) { // 防止重复启动 if (_timerStarted && _refreshTimer != null) { return; } _refreshTimer?.cancel(); _elapsedSeconds = 0; _initialShareBalance = double.tryParse(asset.shareBalance) ?? 0; // 使用传入的每秒增长值(来自 mining-service) _growthPerSecond = double.tryParse(perSecondEarning) ?? 0; _timerStarted = true; _refreshTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (mounted) { setState(() { _elapsedSeconds++; }); } }); } /// 重置定时器(刷新时调用) void _resetTimer() { _refreshTimer?.cancel(); _refreshTimer = null; _elapsedSeconds = 0; _timerStarted = false; } /// 计算当前实时资产显示值 /// 资产显示值 = 积分股余额 × price(不含倍数,使用实际价格) double get _currentDisplayValue { final price = double.tryParse(_lastAsset?.currentPrice ?? '0') ?? 0; return _currentShareBalance * price; } /// 计算当前实时积分股余额 double get _currentShareBalance { return _initialShareBalance + (_elapsedSeconds * _growthPerSecond); } AssetDisplay? _lastAsset; @override Widget build(BuildContext context) { final user = ref.watch(userNotifierProvider); final accountSequence = user.accountSequence ?? ''; // 使用 public API,不依赖 JWT token final assetAsync = ref.watch(accountAssetProvider(accountSequence)); // 从 mining-service 获取每秒收益 final shareAccountAsync = ref.watch(shareAccountProvider(accountSequence)); // 提取数据和加载状态 final isLoading = assetAsync.isLoading || accountSequence.isEmpty; final asset = assetAsync.valueOrNull; final shareAccount = shareAccountAsync.valueOrNull; // 获取每秒收益(优先使用 mining-service 的数据) final perSecondEarning = shareAccount?.perSecondEarning ?? '0'; final hasValidGrowth = (double.tryParse(perSecondEarning) ?? 0) > 0; // 当数据加载完成时启动定时器 if (asset != null && hasValidGrowth) { // 账户切换或首次加载时重置并启动定时器 if (_lastAccountSequence != accountSequence) { _lastAccountSequence = accountSequence; _lastAsset = asset; _resetTimer(); WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _startTimerWithGrowth(asset, perSecondEarning); }); } else if (!_timerStarted) { // 定时器未启动时启动(例如页面刚进入) _lastAsset = asset; WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _startTimerWithGrowth(asset, perSecondEarning); }); } else { _lastAsset = asset; } } else if (asset != null) { _lastAsset = asset; } return Scaffold( backgroundColor: const Color(0xFFF5F5F5), body: SafeArea( bottom: false, child: LayoutBuilder( builder: (context, constraints) { return RefreshIndicator( onRefresh: () async { _resetTimer(); _lastAsset = null; ref.invalidate(accountAssetProvider(accountSequence)); ref.invalidate(shareAccountProvider(accountSequence)); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Column( children: [ // 顶部导航栏 _buildAppBar(context, user), // 内容 Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ const SizedBox(height: 8), // 总资产卡片 - 始终显示,数字部分闪烁,实时刷新 _buildTotalAssetCard(asset, isLoading, _currentDisplayValue, _currentShareBalance, perSecondEarning), const SizedBox(height: 24), // 快捷操作按钮 _buildQuickActions(context), const SizedBox(height: 24), // 资产列表 - 始终显示,数字部分闪烁,实时刷新 _buildAssetList(asset, isLoading, _currentShareBalance, perSecondEarning), const SizedBox(height: 24), // 交易统计 _buildEarningsCard(asset, isLoading), const SizedBox(height: 24), // 账户列表 _buildAccountList(asset, isLoading), const SizedBox(height: 24), ], ), ), ], ), ), ), ); }, ), ), ); } Widget _buildAppBar(BuildContext context, user) { return Container( color: _bgGray.withOpacity(0.9), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: const Center( child: Text( '我的资产', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF111827), ), ), ), ); } Widget _buildTotalAssetCard(AssetDisplay? asset, bool isLoading, double currentDisplayValue, double currentShareBalance, String perSecondEarning) { // 使用传入的每秒增长值(来自 mining-service) final growthPerSecond = double.tryParse(perSecondEarning) ?? 0.0; // 使用实时计算的资产值(如果有)- 不含倍数 final displayValue = asset != null && currentDisplayValue > 0 ? currentDisplayValue.toString() : null; // 使用实时计算的积分股余额(如果有)- 实际数量,不含倍数 final shareBalance = asset != null && currentShareBalance > 0 ? currentShareBalance.toString() : asset?.shareBalance; return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 30, offset: const Offset(0, 8), ), ], ), child: Stack( children: [ // 背景装饰圆 Positioned( right: -20, top: -40, child: Container( width: 128, height: 128, decoration: BoxDecoration( color: _serenade, borderRadius: BorderRadius.circular(64), ), ), ), // 顶部渐变条 Positioned( top: 0, left: 0, right: 0, child: Container( height: 4, decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xFFFF6B00), Color(0xFFFDBA74)], ), borderRadius: BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), ), ), ), // 内容 Padding( padding: const EdgeInsets.fromLTRB(24, 20, 24, 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题行 Row( children: [ const Text( '总资产估值', style: TextStyle( fontSize: 14, color: _grayText, ), ), const SizedBox(width: 8), Icon( Icons.visibility_outlined, size: 14, color: _grayText.withOpacity(0.5), ), ], ), const SizedBox(height: 8), // 金额 - 实时刷新显示 AmountText( amount: displayValue != null ? formatAmount(displayValue) : null, isLoading: isLoading, prefix: '¥ ', style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: _orange, letterSpacing: -0.75, ), ), const SizedBox(height: 4), // 积分股余额 - 实际数量 DataText( data: shareBalance != null ? '≈ ${formatCompact(shareBalance)} 积分股' : null, isLoading: isLoading, placeholder: '≈ -- 积分股', style: const TextStyle( fontSize: 14, color: Color(0xFF9CA3AF), ), ), const SizedBox(height: 12), // 每秒增长 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _feta, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.bolt, size: 14, color: _green), const SizedBox(width: 6), DataText( data: asset != null ? '+${formatDecimal(growthPerSecond.toString(), 8)}/秒' : null, isLoading: isLoading, placeholder: '+--/秒', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _green, ), ), ], ), ), ], ), ), ], ), ); } Widget _buildQuickActions(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildQuickActionItem( Icons.add, '接收', _orange, () => context.push(Routes.receiveShares), ), _buildQuickActionItem( Icons.remove, '发送', _orange, () => context.push(Routes.sendShares), ), _buildQuickActionItem( Icons.people_outline, 'C2C', _orange, () => context.push(Routes.c2cMarket), ), ], ); } Widget _buildQuickActionItem( IconData icon, String label, Color color, VoidCallback onTap, ) { return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, child: Column( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: _serenade, borderRadius: BorderRadius.circular(16), ), child: Icon(icon, color: color, size: 24), ), const SizedBox(height: 8), Text( label, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _riverBed, ), ), ], ), ); } Widget _buildAssetList(AssetDisplay? asset, bool isLoading, double currentShareBalance, String perSecondEarning) { // 使用实时积分股余额 final shareBalance = asset != null && currentShareBalance > 0 ? currentShareBalance : double.tryParse(asset?.shareBalance ?? '0') ?? 0; final multiplier = double.tryParse(asset?.burnMultiplier ?? '0') ?? 0; final multipliedAsset = shareBalance * multiplier; final currentPrice = double.tryParse(asset?.currentPrice ?? '0') ?? 0; return Column( children: [ // 积分股 - 实时刷新 _buildAssetItem( icon: Icons.trending_up, iconColor: _orange, iconBgColor: _serenade, title: '积分股', amount: asset != null ? shareBalance.toString() : null, isLoading: isLoading, valueInCny: asset != null ? '¥${formatAmount((shareBalance * currentPrice).toString())}' : null, tag: asset != null ? '含倍数资产: ${formatCompact(multipliedAsset.toString())}' : null, growthText: asset != null ? '每秒 +${formatDecimal(perSecondEarning, 8)}' : null, ), const SizedBox(height: 16), // 积分值(现金余额) _buildAssetItem( icon: Icons.eco, iconColor: _green, iconBgColor: _feta, title: '积分值', amount: asset?.cashBalance, isLoading: isLoading, valueInCny: asset != null ? '¥${formatAmount(asset.cashBalance)}' : null, ), const SizedBox(height: 16), // 冻结积分股 _buildAssetItem( icon: Icons.lock_outline, iconColor: _orange, iconBgColor: _serenade, title: '冻结积分股', amount: asset?.frozenShares, isLoading: isLoading, subtitle: '交易挂单中', ), ], ); } Widget _buildAssetItem({ required IconData icon, required Color iconColor, required Color iconBgColor, required String title, String? amount, bool isLoading = false, String? valueInCny, String? tag, String? growthText, String? badge, Color? badgeColor, Color? badgeBgColor, String? subtitle, }) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 2, offset: const Offset(0, 1), ), ], ), child: Row( children: [ // 图标 Container( width: 40, height: 40, decoration: BoxDecoration( color: iconBgColor, borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: iconColor, size: 24), ), const SizedBox(width: 12), // 内容 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题行 Row( children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color(0xFF111827), ), ), if (badge != null) ...[ const SizedBox(width: 7), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: badgeBgColor ?? _scandal, borderRadius: BorderRadius.circular(9999), ), child: Text( badge, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: badgeColor ?? _jewel, ), ), ), ], ], ), const SizedBox(height: 2), // 数量 - 闪烁占位符 DataText( data: amount != null ? formatAmount(amount) : null, isLoading: isLoading, placeholder: '--', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF111827), ), ), // 估值 if (valueInCny != null) DataText( data: isLoading ? null : '≈ $valueInCny', isLoading: isLoading, placeholder: '≈ ¥--', style: const TextStyle( fontSize: 12, color: Color(0xFF9CA3AF), ), ), // 副标题 if (subtitle != null) Padding( padding: const EdgeInsets.only(top: 3), child: Text( subtitle, style: const TextStyle( fontSize: 12, color: Color(0xFF9CA3AF), ), ), ), // 标签行 if (tag != null || growthText != null) Padding( padding: const EdgeInsets.only(top: 8), child: Row( children: [ if (tag != null) Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: _serenade, borderRadius: BorderRadius.circular(12), ), child: DataText( data: isLoading ? null : tag, isLoading: isLoading, placeholder: '含倍数资产: --', style: const TextStyle( fontSize: 10, color: _orange, ), ), ), if (growthText != null) ...[ const SizedBox(width: 8), Row( children: [ const Icon(Icons.bolt, size: 12, color: _green), DataText( data: isLoading ? null : growthText, isLoading: isLoading, placeholder: '每秒 +--', style: const TextStyle( fontSize: 10, color: _green, ), ), ], ), ], ], ), ), ], ), ), // 箭头 Icon(Icons.chevron_right, size: 14, color: _grayText.withOpacity(0.5)), ], ), ); } Widget _buildEarningsCard(AssetDisplay? asset, bool isLoading) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 2, offset: const Offset(0, 1), ), ], ), child: Column( children: [ // 标题 Row( children: [ Container( width: 4, height: 20, decoration: BoxDecoration( color: _orange, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), const Text( '交易统计', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _darkText, ), ), ], ), const SizedBox(height: 16), // 统计数据 Row( children: [ _buildEarningsItem( '累计买入', asset != null ? formatCompact(asset.totalBought) : null, _orange, isLoading, ), Container( width: 1, height: 40, color: _serenade, ), _buildEarningsItem( '累计卖出', asset != null ? formatCompact(asset.totalSold) : null, _green, isLoading, ), Container( width: 1, height: 40, color: _serenade, ), _buildEarningsItem( '销毁倍数', asset != null ? '${formatDecimal(asset.burnMultiplier, 4)}x' : null, const Color(0xFF9CA3AF), isLoading, ), ], ), ], ), ); } Widget _buildEarningsItem(String label, String? value, Color valueColor, bool isLoading) { return Expanded( child: Column( children: [ Text( label, style: const TextStyle( fontSize: 12, color: _grayText, ), textAlign: TextAlign.center, ), const SizedBox(height: 4), DataText( data: value, isLoading: isLoading, placeholder: '--', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: valueColor, ), textAlign: TextAlign.center, ), ], ), ); } Widget _buildAccountList(AssetDisplay? asset, bool isLoading) { return Column( children: [ // 交易账户(可用现金) _buildAccountItem( icon: Icons.account_balance_wallet, iconColor: _orange, title: '可用积分值', balance: asset?.availableCash, isLoading: isLoading, unit: '积分值', status: '可交易', statusColor: _green, statusBgColor: _feta, ), const SizedBox(height: 16), // 冻结现金 _buildAccountItem( icon: Icons.lock_outline, iconColor: _orange, title: '冻结积分值', balance: asset?.frozenCash, isLoading: isLoading, unit: '积分值', status: '挂单中', statusColor: const Color(0xFF9CA3AF), statusBgColor: Colors.white, statusBorder: true, ), ], ); } Widget _buildAccountItem({ required IconData icon, required Color iconColor, required String title, String? balance, bool isLoading = false, required String unit, required String status, required Color statusColor, required Color statusBgColor, bool statusBorder = false, }) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 2, offset: const Offset(0, 1), ), ], ), child: Row( children: [ // 图标 Container( width: 36, height: 36, decoration: BoxDecoration( color: _serenade, borderRadius: BorderRadius.circular(18), ), child: Icon(icon, color: iconColor, size: 20), ), const SizedBox(width: 12), // 内容 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF111827), ), ), const SizedBox(height: 2), Row( children: [ DataText( data: balance != null ? formatAmount(balance) : null, isLoading: isLoading, placeholder: '--', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _orange, ), ), Text( ' $unit', style: const TextStyle( fontSize: 12, color: Color(0xFF9CA3AF), ), ), ], ), ], ), ), // 状态标签 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: statusBgColor, borderRadius: BorderRadius.circular(9999), border: statusBorder ? Border.all(color: const Color(0xFFE5E7EB)) : null, ), child: Text( status, style: TextStyle( fontSize: 10, color: statusColor, ), ), ), const SizedBox(width: 8), // 箭头 Icon(Icons.chevron_right, size: 14, color: _grayText.withOpacity(0.5)), ], ), ); } }