import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/utils/number_format.dart'; import '../../../providers/asset_provider.dart'; import '../../../providers/currency_provider.dart'; import '../../../providers/futures_provider.dart' show activeBottomTabProvider, lastTradingRouteProvider; import '../../../providers/spot_coin_cache_provider.dart'; import '../../../providers/spot_provider.dart' show SpotWalletAsset; import '../../widgets/common/app_refresh_indicator.dart'; import '../../widgets/common/coin_icon.dart'; /// 现货 Tab — 展示现货账户总估值、今日盈亏、持仓币种列表 class AssetSpotTradingTab extends ConsumerWidget { const AssetSpotTradingTab( {super.key, required this.state, required this.notifier}); final AssetState state; final AssetNotifier notifier; @override Widget build(BuildContext context, WidgetRef ref) { ref.watch(currencyProvider); // 法币切换触发重建 final cs = Theme.of(context).colorScheme; final l10n = AppLocalizations.of(context)!; final obscure = state.obscureBalance; final total = state.spotTradingTotal; final display = obscure ? '******' : formatPrice(total, decimalPlaces: 2); final hideZero = state.hideZeroBalanceInSpotTab; final assets = state.spotWallets; return AppRefreshIndicator( onRefresh: notifier.silentRefresh, child: ListView( children: [ // 总估值 Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(l10n.totalAssets, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13)), const SizedBox(width: 6), GestureDetector( onTap: notifier.toggleObscure, child: Icon( obscure ? Icons.visibility_off_outlined : Icons.visibility_outlined, size: 16, color: cs.onSurface.withAlpha(153), ), ), ], ), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(display, style: TextStyle( color: cs.onSurface, fontSize: 32, fontWeight: FontWeight.w700, letterSpacing: -0.5)), const SizedBox(width: 6), Padding( padding: const EdgeInsets.only(bottom: 5), child: Text('USDT', style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 14)), ), ], ), ], ), ), // 划转 / 交易 Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), child: Row( children: [ Expanded( child: GestureDetector( onTap: () => context.push('/asset/transfer?from=SPOT&to=SPOT_TRADING'), child: Container( height: 44, decoration: BoxDecoration( color: AppColors.brand, borderRadius: BorderRadius.circular(22)), child: Center( child: Text(l10n.transfer, style: const TextStyle( color: Colors.black, fontSize: 15, fontWeight: FontWeight.w600))), ), ), ), const SizedBox(width: 12), Expanded( child: GestureDetector( onTap: () { const path = '/spot/BTCUSDT'; ref.read(lastTradingRouteProvider.notifier).state = path; ref.read(activeBottomTabProvider.notifier).state = 2; context.go(path); }, child: Container( height: 44, decoration: BoxDecoration( border: Border.all(color: AppColors.brand), borderRadius: BorderRadius.circular(22)), child: Center( child: Text(l10n.goToTrade, style: TextStyle( color: AppColors.brand, fontSize: 15, fontWeight: FontWeight.w600))), ), ), ), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: notifier.toggleHideZeroBalanceInSpotTab, child: Row( children: [ Icon( hideZero ? Icons.check_box_outlined : Icons.check_box_outline_blank, size: 18, color: hideZero ? AppColors.brand : cs.onSurface.withAlpha(140), ), const SizedBox(width: 6), Text( l10n.hideZeroBalanceAssets, style: TextStyle( color: cs.onSurface.withAlpha(170), fontSize: 13, ), ), ], ), ), ], ), ), const SizedBox(height: 8), // 币种列表 if (assets.isEmpty) Padding( padding: const EdgeInsets.symmetric(vertical: 36), child: Center( child: Text(l10n.noAssets, style: TextStyle( color: cs.onSurface.withAlpha(140), fontSize: 13)), ), ) else for (final asset in assets) _SpotAssetRow(asset: asset, obscure: obscure), const SizedBox(height: 32), ], ), ); } } class _SpotAssetRow extends ConsumerWidget { const _SpotAssetRow({required this.asset, required this.obscure}); final SpotWalletAsset asset; final bool obscure; @override Widget build(BuildContext context, WidgetRef ref) { final cs = Theme.of(context).colorScheme; final l10n = AppLocalizations.of(context)!; final mapState = ref.watch(spotCoinCacheProvider); final coinConfig = lookupSpotCoinConfig(mapState, asset.coin); final iconUrl = spotCoinIconUrl(mapState, asset.coin); final decimals = coinConfig?.assetDisplayDecimals ?? 2; final val = obscure ? '******' : null; return Container( padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), decoration: BoxDecoration( border: Border( bottom: BorderSide(color: cs.outline.withAlpha(40), width: 0.6), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 币种图标 + 名称 Row( children: [ CoinIcon( symbol: asset.coin, iconUrl: iconUrl, size: 40, shape: BoxShape.circle), const SizedBox(width: 10), Text(asset.coin, style: TextStyle( color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w700)), ], ), const SizedBox(height: 14), // 三列数据 Row( children: [ Expanded( child: _AssetCell( label: l10n.assetBalance, value: val ?? formatAmount(asset.total, decimals: decimals), align: CrossAxisAlignment.start, ), ), Expanded( child: _AssetCell( label: l10n.availableLabel, value: val ?? formatAmount(asset.balance, decimals: decimals), align: CrossAxisAlignment.center, ), ), Expanded( child: _AssetCell( label: l10n.unavailableLabel, value: val ?? formatAmount(asset.frozenBalance, decimals: decimals), align: CrossAxisAlignment.end, ), ), ], ), ], ), ); } } class _AssetCell extends StatelessWidget { const _AssetCell({ required this.label, required this.value, required this.align, }); final String label; final String value; final CrossAxisAlignment align; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final textAlign = align == CrossAxisAlignment.start ? TextAlign.left : align == CrossAxisAlignment.end ? TextAlign.right : TextAlign.center; return Column( crossAxisAlignment: align, children: [ Text(label, style: TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 11), textAlign: textAlign), const SizedBox(height: 4), Text(value, style: TextStyle( color: cs.onSurface, fontSize: 13, fontWeight: FontWeight.w600, fontFeatures: const [FontFeature.tabularFigures()], ), textAlign: textAlign), ], ); } }