Files
monisuo/flutter_monisuo/lib/ui/pages/trade/trade_page.dart
2026-03-28 23:57:51 +08:00

963 lines
30 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.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 '../../../data/models/coin.dart';
import '../../../providers/market_provider.dart';
import '../../../providers/asset_provider.dart';
import '../../../data/services/trade_service.dart';
import '../../components/glass_panel.dart';
import '../../components/neon_glow.dart';
/// 交易页面
class TradePage extends StatefulWidget {
final String? initialCoinCode;
const TradePage({super.key, this.initialCoinCode});
@override
State<TradePage> createState() => _TradePageState();
}
class _TradePageState extends State<TradePage>
with AutomaticKeepAliveClientMixin {
int _tradeType = 0; // 0=买入, 1=卖出
Coin? _selectedCoin;
final _amountController = TextEditingController();
bool _isSubmitting = false;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}
void _loadData() {
final marketProvider = context.read<MarketProvider>();
marketProvider.loadCoins().then((_) {
if (widget.initialCoinCode != null && _selectedCoin == null) {
final coins = marketProvider.allCoins;
final coin = coins.firstWhere(
(c) =>
c.code.toUpperCase() == widget.initialCoinCode!.toUpperCase(),
orElse: () =>
coins.isNotEmpty ? coins.first : throw Exception('No coins'),
);
if (mounted) setState(() => _selectedCoin = coin);
}
});
context.read<AssetProvider>().refreshAll(force: true);
}
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
/// 获取交易账户中 USDT 可用余额
String get _availableUsdt {
final holdings = context.read<AssetProvider>().holdings;
final usdt = holdings.where((h) => h.coinCode == 'USDT').firstOrNull;
return usdt?.quantity ?? '0';
}
/// 获取交易账户中当前币种的持仓数量
String get _availableCoinQty {
if (_selectedCoin == null) return '0';
final holdings = context.read<AssetProvider>().holdings;
final pos = holdings
.where((h) => h.coinCode == _selectedCoin!.code)
.firstOrNull;
return pos?.quantity ?? '0';
}
/// 计算可买入/卖出的最大 USDT 金额
String get _maxAmount {
if (_selectedCoin == null) return '0';
final price = _selectedCoin!.price;
if (price <= 0) return '0';
if (_tradeType == 0) {
// 买入:最大 = USDT 余额
return _availableUsdt;
} else {
// 卖出:最大 = 持有数量 × 当前价格
final qty = double.tryParse(_availableCoinQty) ?? 0;
return (qty * price).toStringAsFixed(2);
}
}
/// 计算数量
String get _calculatedQuantity {
final amount = double.tryParse(_amountController.text) ?? 0;
final price = _selectedCoin?.price ?? 0;
if (price <= 0 || amount <= 0) return '0';
return (amount / price).toStringAsFixed(6);
}
@override
Widget build(BuildContext context) {
super.build(context);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.background,
body: Consumer2<MarketProvider, AssetProvider>(
builder: (context, market, asset, _) {
return SingleChildScrollView(
padding: AppSpacing.pagePadding,
child: Column(
children: [
// 币种选择器
_CoinSelector(
selectedCoin: _selectedCoin,
coins: market.allCoins
.where((c) => c.code != 'USDT')
.toList(),
onCoinSelected: (coin) {
setState(() {
_selectedCoin = coin;
_amountController.clear();
});
},
),
SizedBox(height: AppSpacing.md),
// 价格卡片
if (_selectedCoin != null)
_PriceCard(coin: _selectedCoin!)
else
_PlaceholderCard(message: '请先选择交易币种'),
SizedBox(height: AppSpacing.md),
// 交易表单
_TradeFormCard(
tradeType: _tradeType,
selectedCoin: _selectedCoin,
amountController: _amountController,
availableUsdt: _availableUsdt,
availableCoinQty: _availableCoinQty,
calculatedQuantity: _calculatedQuantity,
maxAmount: _maxAmount,
onTradeTypeChanged: (type) => setState(() {
_tradeType = type;
_amountController.clear();
}),
onAmountChanged: () => setState(() {}),
onFillPercent: (pct) => _fillPercent(pct),
),
SizedBox(height: AppSpacing.lg),
// 买入 + 卖出双按钮
Row(
children: [
Expanded(
child: _TradeButton(
isBuy: true,
coinCode: _selectedCoin?.code,
enabled: _canTrade() && !_isSubmitting,
isLoading: _isSubmitting && _tradeType == 0,
onPressed: () {
_tradeType = 0;
_executeTrade();
},
),
),
SizedBox(width: AppSpacing.md),
Expanded(
child: _TradeButton(
isBuy: false,
coinCode: _selectedCoin?.code,
enabled: _canTrade() && !_isSubmitting,
isLoading: _isSubmitting && _tradeType == 1,
onPressed: () {
_tradeType = 1;
_executeTrade();
},
),
),
],
),
],
),
);
},
),
);
}
bool _canTrade() {
if (_selectedCoin == null) return false;
final amount = double.tryParse(_amountController.text) ?? 0;
return amount > 0;
}
void _fillPercent(double pct) {
final max = double.tryParse(_maxAmount) ?? 0;
_amountController.text = (max * pct).toStringAsFixed(2);
setState(() {});
}
void _executeTrade() async {
final isBuy = _tradeType == 0;
final amount = _amountController.text;
final quantity = _calculatedQuantity;
final price = _selectedCoin!.price.toStringAsFixed(2);
final coinCode = _selectedCoin!.code;
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => _ConfirmDialog(
isBuy: isBuy,
coinCode: coinCode,
price: price,
quantity: quantity,
amount: amount,
),
);
if (confirmed != true) return;
setState(() => _isSubmitting = true);
try {
final tradeService = context.read<TradeService>();
final response = isBuy
? await tradeService.buy(
coinCode: coinCode, price: price, quantity: quantity)
: await tradeService.sell(
coinCode: coinCode, price: price, quantity: quantity);
if (!mounted) return;
if (response.success) {
_amountController.clear();
// 刷新资产数据
context.read<AssetProvider>().refreshAll(force: true);
_showResultDialog(true, '${isBuy ? '买入' : '卖出'}成功',
'$quantity $coinCode @ $price USDT');
} else {
_showResultDialog(false, '交易失败', response.message ?? '请稍后重试');
}
} catch (e) {
if (mounted) {
_showResultDialog(false, '交易失败', e.toString());
}
} finally {
if (mounted) setState(() => _isSubmitting = false);
}
}
void _showResultDialog(bool success, String title, String message) {
final colorScheme = Theme.of(context).colorScheme;
showShadDialog(
context: context,
builder: (ctx) => ShadDialog.alert(
title: Row(
children: [
NeonIcon(
icon: success ? Icons.check_circle : Icons.error,
color: success ? AppColorScheme.up : colorScheme.error,
size: 24,
),
SizedBox(width: AppSpacing.sm),
Text(title),
],
),
description: Text(message),
actions: [
ShadButton(
child: const Text('确定'),
onPressed: () => Navigator.of(ctx).pop(),
),
],
),
);
}
}
/// 确认对话框
class _ConfirmDialog extends StatelessWidget {
final bool isBuy;
final String coinCode;
final String price;
final String quantity;
final String amount;
const _ConfirmDialog({
required this.isBuy,
required this.coinCode,
required this.price,
required this.quantity,
required this.amount,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final actionColor = isBuy ? AppColorScheme.up : AppColorScheme.down;
return 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: [
Center(
child: Text(
'确认${isBuy ? '买入' : '卖出'}',
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
),
SizedBox(height: AppSpacing.lg),
_dialogRow('交易对', '$coinCode/USDT', colorScheme),
SizedBox(height: AppSpacing.sm),
_dialogRow('委托价格', '$price USDT', colorScheme),
SizedBox(height: AppSpacing.sm),
_dialogRow('交易金额', '$amount USDT', colorScheme,
valueColor: actionColor),
SizedBox(height: AppSpacing.sm),
_dialogRow('交易数量', '$quantity $coinCode', colorScheme),
SizedBox(height: AppSpacing.lg),
Row(
children: [
Expanded(
child: NeonButton(
text: '取消',
type: NeonButtonType.outline,
onPressed: () => Navigator.of(context).pop(false),
height: 44,
showGlow: false,
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: NeonButton(
text: '确认${isBuy ? '买入' : '卖出'}',
type: isBuy ? NeonButtonType.tertiary : NeonButtonType.error,
onPressed: () => Navigator.of(context).pop(true),
height: 44,
showGlow: true,
),
),
],
),
],
),
),
);
}
Widget _dialogRow(String label, String value, ColorScheme colorScheme,
{Color? valueColor}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style: TextStyle(
fontSize: 14, color: colorScheme.onSurfaceVariant)),
Text(value,
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: valueColor ?? colorScheme.onSurface,
)),
],
);
}
}
/// 币种选择器
class _CoinSelector extends StatelessWidget {
final Coin? selectedCoin;
final List<Coin> coins;
final ValueChanged<Coin> onCoinSelected;
const _CoinSelector({
required this.selectedCoin,
required this.coins,
required this.onCoinSelected,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => _showCoinPicker(context),
child: GlassPanel(
padding: EdgeInsets.all(AppSpacing.md),
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: GoogleFonts.spaceGrotesk(
fontSize: 18,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
SizedBox(height: AppSpacing.xs),
Text(
selectedCoin?.name ?? '点击选择交易对',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
Icon(LucideIcons.chevronDown, color: colorScheme.onSurfaceVariant),
],
),
),
);
}
void _showCoinPicker(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (ctx) => Container(
height: MediaQuery.of(ctx).size.height * 0.65,
decoration: BoxDecoration(
color: isDark
? colorScheme.surface
: colorScheme.surfaceContainerLowest,
borderRadius:
BorderRadius.vertical(top: Radius.circular(AppRadius.xxl)),
),
child: Column(
children: [
Container(
margin: EdgeInsets.only(top: AppSpacing.sm),
width: 40,
height: 4,
decoration: BoxDecoration(
color: colorScheme.onSurfaceVariant.withOpacity(0.3),
borderRadius: BorderRadius.circular(2),
),
),
Padding(
padding: EdgeInsets.all(AppSpacing.lg),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('选择币种',
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
)),
GestureDetector(
onTap: () => Navigator.of(ctx).pop(),
child: Icon(LucideIcons.x,
color: colorScheme.onSurfaceVariant),
),
],
),
),
Divider(height: 1, color: colorScheme.outlineVariant.withOpacity(0.2)),
Expanded(
child: ListView.builder(
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm),
itemCount: coins.length,
itemBuilder: (listCtx, index) =>
_buildCoinItem(coins[index], context, listCtx),
),
),
],
),
),
);
}
Widget _buildCoinItem(
Coin coin, BuildContext context, BuildContext sheetContext) {
final colorScheme = Theme.of(context).colorScheme;
final isSelected = selectedCoin?.code == coin.code;
final changeColor = coin.isUp ? AppColorScheme.up : AppColorScheme.down;
return GestureDetector(
onTap: () {
Navigator.of(sheetContext).pop();
onCoinSelected(coin);
},
child: Container(
padding:
EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: AppSpacing.md),
color:
isSelected ? colorScheme.primary.withOpacity(0.1) : Colors.transparent,
child: Row(
children: [
_CoinAvatar(icon: coin.displayIcon),
SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(coin.code,
style: GoogleFonts.spaceGrotesk(
fontSize: 16,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
)),
SizedBox(width: AppSpacing.xs),
Text('/USDT',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
)),
],
),
Text(coin.name,
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('\$${coin.formattedPrice}',
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
)),
Text(coin.formattedChange,
style: TextStyle(
fontSize: 12,
color: changeColor,
fontWeight: FontWeight.w600,
)),
],
),
if (isSelected) ...[
SizedBox(width: AppSpacing.sm),
Icon(LucideIcons.check, size: 18, color: colorScheme.primary),
],
],
),
),
);
}
}
/// 币种头像
class _CoinAvatar extends StatelessWidget {
final String? icon;
const _CoinAvatar({this.icon});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: colorScheme.primary.withOpacity(0.2)),
),
child: Center(
child: Text(icon ?? '?',
style: TextStyle(
fontSize: 20,
color: colorScheme.primary,
fontWeight: FontWeight.bold,
)),
),
);
}
}
/// 价格卡片
class _PriceCard extends StatelessWidget {
final Coin coin;
const _PriceCard({required this.coin});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
final color =
coin.isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down;
final bgColor = coin.isUp
? AppColorScheme.getUpBackgroundColor(isDark)
: colorScheme.error.withOpacity(0.1);
return GlassPanel(
padding: EdgeInsets.all(AppSpacing.lg),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('最新价',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
)),
SizedBox(height: AppSpacing.xs),
Text(
'\$${coin.formattedPrice}',
style: GoogleFonts.spaceGrotesk(
fontSize: 28,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
],
),
Container(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.md, vertical: AppSpacing.sm),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(color: color.withOpacity(0.2)),
),
child: Text(
coin.formattedChange,
style: TextStyle(
fontSize: 16, color: color, fontWeight: FontWeight.w700),
),
),
],
),
);
}
}
/// 占位卡片
class _PlaceholderCard extends StatelessWidget {
final String message;
const _PlaceholderCard({required this.message});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GlassPanel(
padding: EdgeInsets.all(AppSpacing.xl),
child: Center(
child: Text(message,
style: TextStyle(
color: colorScheme.onSurfaceVariant,
fontSize: 14,
)),
),
);
}
}
/// 交易表单卡片
class _TradeFormCard extends StatelessWidget {
final int tradeType;
final Coin? selectedCoin;
final TextEditingController amountController;
final String availableUsdt;
final String availableCoinQty;
final String calculatedQuantity;
final String maxAmount;
final ValueChanged<int> onTradeTypeChanged;
final VoidCallback onAmountChanged;
final ValueChanged<double> onFillPercent;
const _TradeFormCard({
required this.tradeType,
required this.selectedCoin,
required this.amountController,
required this.availableUsdt,
required this.availableCoinQty,
required this.calculatedQuantity,
required this.maxAmount,
required this.onTradeTypeChanged,
required this.onAmountChanged,
required this.onFillPercent,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isBuy = tradeType == 0;
final actionColor = isBuy ? AppColorScheme.up : AppColorScheme.down;
return GlassPanel(
padding: EdgeInsets.all(AppSpacing.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 买入/卖出切换
Container(
padding: EdgeInsets.all(AppSpacing.xs),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.xl),
),
child: Row(
children: [
Expanded(
child: _typeButton('买入', isBuy, AppColorScheme.up, () => onTradeTypeChanged(0)),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: _typeButton('卖出', !isBuy, AppColorScheme.down, () => onTradeTypeChanged(1)),
),
],
),
),
SizedBox(height: AppSpacing.lg),
// 交易金额输入
Text('交易金额 (USDT)',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
color: colorScheme.onSurfaceVariant,
)),
SizedBox(height: AppSpacing.xs),
Container(
decoration: BoxDecoration(
color: colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: colorScheme.outlineVariant.withOpacity(0.3)),
),
child: TextField(
controller: amountController,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: (_) => onAmountChanged(),
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
decoration: InputDecoration(
hintText: '输入金额',
hintStyle: TextStyle(
color: colorScheme.outlineVariant.withOpacity(0.5)),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.md,
),
suffixIcon: Padding(
padding: EdgeInsets.only(right: AppSpacing.sm),
child: Text('USDT',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: colorScheme.onSurfaceVariant,
)),
),
suffixIconConstraints: const BoxConstraints(minWidth: 50),
),
),
),
SizedBox(height: AppSpacing.sm),
// 快捷比例按钮
Row(
children: [
_pctButton('25%', 0.25, colorScheme),
SizedBox(width: AppSpacing.xs),
_pctButton('50%', 0.5, colorScheme),
SizedBox(width: AppSpacing.xs),
_pctButton('75%', 0.75, colorScheme),
SizedBox(width: AppSpacing.xs),
_pctButton('全部', 1.0, colorScheme),
],
),
SizedBox(height: AppSpacing.lg),
// 预计数量
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('预计数量',
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
)),
Text(
'$calculatedQuantity ${selectedCoin?.code ?? ''}',
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
],
),
SizedBox(height: AppSpacing.md),
// 可用余额
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(isBuy ? '可用 USDT' : '可用 ${selectedCoin?.code ?? ""}',
style: TextStyle(
fontSize: 13,
color: colorScheme.onSurfaceVariant,
)),
Text(
isBuy
? '$availableUsdt USDT'
: '$availableCoinQty ${selectedCoin?.code ?? ""}',
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
],
),
],
),
);
}
Widget _typeButton(
String label, bool isActive, Color color, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs),
decoration: BoxDecoration(
color: isActive ? color.withOpacity(0.15) : Colors.transparent,
borderRadius: BorderRadius.circular(AppRadius.md),
border: isActive ? null : Border.all(color: color.withOpacity(0.3)),
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isActive ? color : color.withOpacity(0.7),
fontWeight: FontWeight.w700,
fontSize: 14,
letterSpacing: 0.5,
),
),
),
),
);
}
Widget _pctButton(String label, double pct, ColorScheme colorScheme) {
return Expanded(
child: GestureDetector(
onTap: () => onFillPercent(pct),
child: Container(
padding: EdgeInsets.symmetric(vertical: AppSpacing.xs + 2),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Center(
child: Text(label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: colorScheme.onSurfaceVariant,
)),
),
),
),
);
}
}
/// 交易按钮
class _TradeButton extends StatelessWidget {
final bool isBuy;
final String? coinCode;
final bool enabled;
final bool isLoading;
final VoidCallback onPressed;
const _TradeButton({
required this.isBuy,
required this.coinCode,
required this.enabled,
required this.isLoading,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: enabled ? onPressed : null,
style: ElevatedButton.styleFrom(
backgroundColor:
isBuy ? AppColorScheme.up : AppColorScheme.down,
disabledBackgroundColor: colorScheme.onSurface.withOpacity(0.12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.xl),
),
elevation: isBuy && enabled ? 4 : 0,
shadowColor:
isBuy ? AppColorScheme.up.withOpacity(0.3) : Colors.transparent,
),
child: isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: isBuy ? Colors.black87 : Colors.white,
),
)
: Text(
'${isBuy ? '买入' : '卖出'} ${coinCode ?? ""}',
style: GoogleFonts.spaceGrotesk(
fontSize: 16,
fontWeight: FontWeight.w700,
color: isBuy ? Colors.black87 : Colors.white,
letterSpacing: 1,
),
),
),
);
}
}