import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../data/models/coin.dart'; import '../../../providers/market_provider.dart'; import '../../../providers/asset_provider.dart'; import '../../shared/ui_constants.dart'; /// 交易页面 - 使用 shadcn_ui 现代化设计 class TradePage extends StatefulWidget { const TradePage({super.key}); @override State createState() => _TradePageState(); } class _TradePageState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; int _tradeType = 0; // 0=买入, 1=卖出 Coin? _selectedCoin; final _formKey = GlobalKey(); final _priceController = TextEditingController(); final _quantityController = TextEditingController(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } void _loadData() { context.read().loadCoins(); } @override void dispose() { _priceController.dispose(); _quantityController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { super.build(context); final theme = ShadTheme.of(context); return Scaffold( backgroundColor: theme.colorScheme.background, body: Consumer2( builder: (context, market, asset, _) { return SingleChildScrollView( padding: AppSpacing.pagePadding, child: ShadForm( key: _formKey, child: Column( children: [ _CoinSelector( selectedCoin: _selectedCoin, coins: market.allCoins, onCoinLoaded: (coin) { _selectedCoin = coin; _priceController.text = coin.formattedPrice; }, ), SizedBox(height: AppSpacing.md), if (_selectedCoin != null) _PriceCard(coin: _selectedCoin!), SizedBox(height: AppSpacing.md), _TradeForm( tradeType: _tradeType, selectedCoin: _selectedCoin, priceController: _priceController, quantityController: _quantityController, tradeBalance: asset.overview?.tradeBalance, onTradeTypeChanged: (type) => setState(() => _tradeType = type), ), SizedBox(height: AppSpacing.md), _TradeButton( isBuy: _tradeType == 0, coinCode: _selectedCoin?.code, onPressed: () { if (_formKey.currentState!.saveAndValidate()) { _executeTrade(); } }, ), ], ), ), ); }, ), ); } void _executeTrade() { final price = _priceController.text; final quantity = _quantityController.text; final isBuy = _tradeType == 0; showShadDialog( context: context, builder: (ctx) => ShadDialog.alert( title: Text(isBuy ? '确认买入' : '确认卖出'), description: Text('${isBuy ? '买入' : '卖出'} $quantity ${_selectedCoin?.code ?? ''} @ $price USDT'), actions: [ ShadButton.outline(child: const Text('取消'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('确认'), onPressed: () { Navigator.of(ctx).pop(); _showTradeResult(); }, ), ], ), ); } void _showTradeResult() { final theme = ShadTheme.of(context); final isBuy = _tradeType == 0; showShadDialog( context: context, builder: (ctx) => ShadDialog.alert( title: Row( children: [ Icon(LucideIcons.circleCheck, color: theme.colorScheme.primary, size: 24), SizedBox(width: AppSpacing.sm), const Text('交易成功'), ], ), description: Text('已${isBuy ? '买入' : '卖出'} ${_quantityController.text} ${_selectedCoin?.code ?? ''}'), actions: [ ShadButton( child: const Text('确定'), onPressed: () { Navigator.of(ctx).pop(); _quantityController.clear(); }, ), ], ), ); } } /// 币种选择器 class _CoinSelector extends StatelessWidget { final Coin? selectedCoin; final List coins; final ValueChanged onCoinLoaded; const _CoinSelector({ required this.selectedCoin, required this.coins, required this.onCoinLoaded, }); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); // 自动选择第一个币种 if (selectedCoin == null && coins.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) => onCoinLoaded(coins.first)); } return ShadCard( padding: AppSpacing.cardPadding, child: Row( children: [ _CoinAvatar(icon: selectedCoin?.displayIcon), SizedBox(width: AppSpacing.sm + AppSpacing.xs), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( selectedCoin != null ? '${selectedCoin!.code}/USDT' : '选择币种', style: theme.textTheme.large.copyWith(fontWeight: FontWeight.bold), ), SizedBox(height: AppSpacing.xs), Text(selectedCoin?.name ?? '点击选择交易对', style: theme.textTheme.muted), ], ), ), Icon(LucideIcons.chevronRight, color: theme.colorScheme.mutedForeground), ], ), ); } } /// 币种头像 class _CoinAvatar extends StatelessWidget { final String? icon; const _CoinAvatar({this.icon}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return CircleAvatar( radius: 22, backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.1), child: Text( icon ?? '?', style: TextStyle(fontSize: 20, color: theme.colorScheme.primary), ), ); } } /// 价格卡片 class _PriceCard extends StatelessWidget { final Coin coin; const _PriceCard({required this.coin}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); final color = coin.isUp ? AppColorScheme.up : AppColorScheme.down; return ShadCard( padding: AppSpacing.cardPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('最新价', style: theme.textTheme.muted), SizedBox(height: AppSpacing.xs), Text('\$${coin.formattedPrice}', style: theme.textTheme.h2.copyWith(fontWeight: FontWeight.bold)), ], ), Container( padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm + AppSpacing.xs, vertical: AppSpacing.xs + AppSpacing.xs), decoration: BoxDecoration( color: color.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Text( coin.formattedChange, style: TextStyle(fontSize: 16, color: color, fontWeight: FontWeight.w600), ), ), ], ), ); } } /// 交易表单 class _TradeForm extends StatelessWidget { final int tradeType; final Coin? selectedCoin; final TextEditingController priceController; final TextEditingController quantityController; final String? tradeBalance; final ValueChanged onTradeTypeChanged; const _TradeForm({ required this.tradeType, required this.selectedCoin, required this.priceController, required this.quantityController, required this.tradeBalance, required this.onTradeTypeChanged, }); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return ShadCard( padding: AppSpacing.cardPadding, child: Column( children: [ // 买入/卖出切换 _TradeTypeSelector( tradeType: tradeType, onChanged: onTradeTypeChanged, ), SizedBox(height: AppSpacing.lg + AppSpacing.xs), // 价格输入 ShadInputFormField( id: 'price', label: const Text('价格(USDT)'), controller: priceController, keyboardType: const TextInputType.numberWithOptions(decimal: true), placeholder: const Text('输入价格'), trailing: Padding( padding: EdgeInsets.only(right: AppSpacing.sm), child: const Text('USDT'), ), validator: Validators.price, ), SizedBox(height: AppSpacing.md), // 数量输入 ShadInputFormField( id: 'quantity', label: const Text('数量'), controller: quantityController, keyboardType: const TextInputType.numberWithOptions(decimal: true), placeholder: const Text('输入数量'), trailing: Padding( padding: EdgeInsets.only(right: AppSpacing.sm), child: Text(selectedCoin?.code ?? ''), ), validator: Validators.quantity, ), SizedBox(height: AppSpacing.md), // 交易金额 _InfoRow(label: '交易金额', value: '${_calculateAmount()} USDT'), SizedBox(height: AppSpacing.sm), // 可用余额 _InfoRow(label: '可用', value: '${tradeBalance ?? '0.00'} USDT'), ], ), ); } String _calculateAmount() { final price = double.tryParse(priceController.text) ?? 0; final quantity = double.tryParse(quantityController.text) ?? 0; return (price * quantity).toStringAsFixed(2); } } /// 交易类型选择器 class _TradeTypeSelector extends StatelessWidget { final int tradeType; final ValueChanged onChanged; const _TradeTypeSelector({required this.tradeType, required this.onChanged}); @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: _TypeButton( label: '买入', isSelected: tradeType == 0, color: AppColorScheme.up, onTap: () => onChanged(0), ), ), SizedBox(width: AppSpacing.md), Expanded( child: _TypeButton( label: '卖出', isSelected: tradeType == 1, color: AppColorScheme.down, onTap: () => onChanged(1), ), ), ], ); } } /// 类型按钮 class _TypeButton extends StatelessWidget { final String label; final bool isSelected; final Color color; final VoidCallback onTap; const _TypeButton({ required this.label, required this.isSelected, required this.color, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs), decoration: BoxDecoration( color: isSelected ? color : Colors.transparent, borderRadius: BorderRadius.circular(AppRadius.md), border: isSelected ? null : Border.all(color: color), ), child: Center( child: Text( label, style: TextStyle( color: isSelected ? Colors.white : color, fontWeight: FontWeight.w600, ), ), ), ), ); } } /// 信息行 class _InfoRow extends StatelessWidget { final String label; final String value; const _InfoRow({required this.label, required this.value}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: theme.textTheme.muted), Text(value, style: theme.textTheme.small.copyWith(fontWeight: FontWeight.w600)), ], ); } } /// 交易按钮 class _TradeButton extends StatelessWidget { final bool isBuy; final String? coinCode; final VoidCallback onPressed; const _TradeButton({ required this.isBuy, required this.coinCode, required this.onPressed, }); @override Widget build(BuildContext context) { final color = isBuy ? AppColorScheme.up : AppColorScheme.down; return SizedBox( width: double.infinity, height: 48, child: ShadButton( backgroundColor: color, onPressed: onPressed, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(isBuy ? LucideIcons.arrowDownToLine : LucideIcons.arrowUpFromLine, size: 18, color: Colors.white), SizedBox(width: AppSpacing.sm), Text( '${isBuy ? '买入' : '卖出'} ${coinCode ?? ''}', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600), ), ], ), ), ); } }