111
This commit is contained in:
@@ -84,10 +84,8 @@ class _TradePageState extends State<TradePage>
|
||||
if (price <= 0) return '0';
|
||||
|
||||
if (_tradeType == 0) {
|
||||
// 买入:最大 = USDT 余额
|
||||
return _availableUsdt;
|
||||
} else {
|
||||
// 卖出:最大 = 持有数量 × 当前价格
|
||||
final qty = double.tryParse(_availableCoinQty) ?? 0;
|
||||
return (qty * price).toStringAsFixed(2);
|
||||
}
|
||||
@@ -118,7 +116,10 @@ class _TradePageState extends State<TradePage>
|
||||
_CoinSelector(
|
||||
selectedCoin: _selectedCoin,
|
||||
coins: market.allCoins
|
||||
.where((c) => c.code != 'USDT')
|
||||
.where((c) =>
|
||||
c.code != 'USDT' &&
|
||||
c.code != 'BTC' &&
|
||||
c.code != 'ETH')
|
||||
.toList(),
|
||||
onCoinSelected: (coin) {
|
||||
setState(() {
|
||||
@@ -133,7 +134,7 @@ class _TradePageState extends State<TradePage>
|
||||
if (_selectedCoin != null)
|
||||
_PriceCard(coin: _selectedCoin!)
|
||||
else
|
||||
_PlaceholderCard(message: '请先选择交易币种'),
|
||||
const _PlaceholderCard(message: '请先选择交易币种'),
|
||||
|
||||
SizedBox(height: AppSpacing.md),
|
||||
|
||||
@@ -197,7 +198,13 @@ class _TradePageState extends State<TradePage>
|
||||
bool _canTrade() {
|
||||
if (_selectedCoin == null) return false;
|
||||
final amount = double.tryParse(_amountController.text) ?? 0;
|
||||
return amount > 0;
|
||||
if (amount <= 0) return false;
|
||||
// 买入时校验不超过可用USDT
|
||||
if (_tradeType == 0) {
|
||||
final available = double.tryParse(_availableUsdt) ?? 0;
|
||||
if (amount > available) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _fillPercent(double pct) {
|
||||
@@ -524,22 +531,52 @@ class _CoinSelector extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 第一行:币种代码 + 价格 + 涨跌幅
|
||||
Row(
|
||||
children: [
|
||||
Text(coin.code,
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
)),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Text('/USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontSize: 11,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
)),
|
||||
const Spacer(),
|
||||
Text('\$${coin.formattedPrice}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
)),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: changeColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text(coin.formattedChange,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: changeColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
)),
|
||||
),
|
||||
if (isSelected) ...[
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Icon(LucideIcons.check,
|
||||
size: 16, color: colorScheme.primary),
|
||||
],
|
||||
],
|
||||
),
|
||||
SizedBox(height: 3),
|
||||
// 第二行:币种名称
|
||||
Text(coin.name,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
@@ -548,27 +585,6 @@ class _CoinSelector extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
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),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -604,7 +620,7 @@ class _CoinAvatar extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 价格卡片
|
||||
/// 价格卡片 - 重新设计
|
||||
class _PriceCard extends StatelessWidget {
|
||||
final Coin coin;
|
||||
const _PriceCard({required this.coin});
|
||||
@@ -613,48 +629,81 @@ class _PriceCard extends StatelessWidget {
|
||||
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
|
||||
final isUp = coin.isUp;
|
||||
final changeColor =
|
||||
isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down;
|
||||
final changeBgColor = isUp
|
||||
? AppColorScheme.getUpBackgroundColor(isDark)
|
||||
: colorScheme.error.withOpacity(0.1);
|
||||
|
||||
return GlassPanel(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.lg, vertical: AppSpacing.md + AppSpacing.sm),
|
||||
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,
|
||||
// 左侧:币种标签 + 价格
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text(
|
||||
'${coin.code}/USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
'\$${coin.formattedPrice}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 右侧:涨跌幅
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md, vertical: AppSpacing.sm),
|
||||
horizontal: AppSpacing.md, vertical: AppSpacing.sm + 2),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
border: Border.all(color: color.withOpacity(0.2)),
|
||||
color: changeBgColor,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(color: changeColor.withOpacity(0.15)),
|
||||
),
|
||||
child: Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: color, fontWeight: FontWeight.w700),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'24h',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: changeColor.withOpacity(0.7),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
coin.formattedChange,
|
||||
style: TextStyle(
|
||||
fontSize: 16, color: changeColor, fontWeight: FontWeight.w700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -684,7 +733,7 @@ class _PlaceholderCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 交易表单卡片
|
||||
/// 交易表单卡片 - 重新设计
|
||||
class _TradeFormCard extends StatelessWidget {
|
||||
final int tradeType;
|
||||
final Coin? selectedCoin;
|
||||
@@ -721,9 +770,9 @@ class _TradeFormCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 买入/卖出切换
|
||||
// 买入/卖出切换 - 重新设计
|
||||
Container(
|
||||
padding: EdgeInsets.all(AppSpacing.xs),
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
@@ -731,11 +780,25 @@ class _TradeFormCard extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _typeButton('买入', isBuy, AppColorScheme.up, () => onTradeTypeChanged(0)),
|
||||
child: _buildTypeButton(
|
||||
context: context,
|
||||
label: '买入',
|
||||
isActive: isBuy,
|
||||
color: AppColorScheme.up,
|
||||
icon: LucideIcons.trendingUp,
|
||||
onTap: () => onTradeTypeChanged(0),
|
||||
),
|
||||
),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: _typeButton('卖出', !isBuy, AppColorScheme.down, () => onTradeTypeChanged(1)),
|
||||
child: _buildTypeButton(
|
||||
context: context,
|
||||
label: '卖出',
|
||||
isActive: !isBuy,
|
||||
color: AppColorScheme.down,
|
||||
icon: LucideIcons.trendingDown,
|
||||
onTap: () => onTradeTypeChanged(1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -743,157 +806,188 @@ class _TradeFormCard extends StatelessWidget {
|
||||
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),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('交易金额',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurface,
|
||||
)),
|
||||
Text('USDT',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: actionColor.withOpacity(0.7),
|
||||
letterSpacing: 0.5,
|
||||
)),
|
||||
],
|
||||
),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
_AmountInput(
|
||||
amountController: amountController,
|
||||
maxAmount: maxAmount,
|
||||
isBuy: isBuy,
|
||||
actionColor: actionColor,
|
||||
onChanged: onAmountChanged,
|
||||
),
|
||||
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),
|
||||
_buildPctButton('25%', 0.25, colorScheme, actionColor),
|
||||
SizedBox(width: 6),
|
||||
_buildPctButton('50%', 0.5, colorScheme, actionColor),
|
||||
SizedBox(width: 6),
|
||||
_buildPctButton('75%', 0.75, colorScheme, actionColor),
|
||||
SizedBox(width: 6),
|
||||
_buildPctButton('全部', 1.0, colorScheme, actionColor),
|
||||
],
|
||||
),
|
||||
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,
|
||||
// 预计数量 + 可用余额 - 卡片样式
|
||||
Container(
|
||||
padding: EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withOpacity(0.1)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 预计数量
|
||||
Row(
|
||||
children: [
|
||||
Icon(LucideIcons.calculator,
|
||||
size: 14, color: colorScheme.onSurfaceVariant),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Text('预计数量',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
)),
|
||||
const Spacer(),
|
||||
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,
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm),
|
||||
child: Divider(
|
||||
height: 1,
|
||||
color: colorScheme.outlineVariant.withOpacity(0.08)),
|
||||
),
|
||||
),
|
||||
],
|
||||
// 可用余额
|
||||
Row(
|
||||
children: [
|
||||
Icon(LucideIcons.wallet,
|
||||
size: 14, color: colorScheme.onSurfaceVariant),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
Text(isBuy ? '可用 USDT' : '可用 ${selectedCoin?.code ?? ""}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
)),
|
||||
const Spacer(),
|
||||
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) {
|
||||
/// 买入/卖出切换按钮 - 实心填充 + 图标 + 光效
|
||||
Widget _buildTypeButton({
|
||||
required BuildContext context,
|
||||
required String label,
|
||||
required bool isActive,
|
||||
required Color color,
|
||||
required IconData icon,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + 4),
|
||||
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)),
|
||||
color: isActive ? color : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
boxShadow: isActive
|
||||
? [
|
||||
BoxShadow(
|
||||
color: color.withOpacity(0.35),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isActive ? color : color.withOpacity(0.7),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon,
|
||||
size: 16,
|
||||
color: isActive ? Colors.white : color.withOpacity(0.5)),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isActive ? Colors.white : color.withOpacity(0.5),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 15,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _pctButton(String label, double pct, ColorScheme colorScheme) {
|
||||
/// 百分比按钮 - 药丸样式
|
||||
Widget _buildPctButton(
|
||||
String label, double pct, ColorScheme colorScheme, Color actionColor) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => onFillPercent(pct),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.xs + 2),
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm - 2),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
color: actionColor.withOpacity(0.06),
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
border: Border.all(color: actionColor.withOpacity(0.12)),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
color: actionColor.withOpacity(0.8),
|
||||
)),
|
||||
),
|
||||
),
|
||||
@@ -902,7 +996,7 @@ class _TradeFormCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// 交易按钮
|
||||
/// 交易按钮 - 使用 NeonButton 风格
|
||||
class _TradeButton extends StatelessWidget {
|
||||
final bool isBuy;
|
||||
final String? coinCode;
|
||||
@@ -921,42 +1015,199 @@ class _TradeButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final actionColor = isBuy ? AppColorScheme.up : AppColorScheme.down;
|
||||
final gradient = isBuy
|
||||
? AppColorScheme.getBuyGradient(isDark)
|
||||
: AppColorScheme.sellGradient;
|
||||
|
||||
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,
|
||||
return GestureDetector(
|
||||
onTap: enabled ? onPressed : null,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
gradient: enabled ? gradient : null,
|
||||
color: enabled ? null : colorScheme.onSurface.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
boxShadow: enabled
|
||||
? [
|
||||
BoxShadow(
|
||||
color: actionColor.withOpacity(isDark ? 0.25 : 0.15),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: isBuy ? Colors.black87 : Colors.white,
|
||||
child: Center(
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: isBuy
|
||||
? AppColorScheme.darkOnTertiary
|
||||
: Colors.white,
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isBuy ? LucideIcons.trendingUp : LucideIcons.trendingDown,
|
||||
size: 16,
|
||||
color: enabled
|
||||
? (isBuy
|
||||
? AppColorScheme.darkOnTertiary
|
||||
: Colors.white)
|
||||
: colorScheme.onSurface.withOpacity(0.3),
|
||||
),
|
||||
SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'${isBuy ? '买入' : '卖出'} ${coinCode ?? ""}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: enabled
|
||||
? (isBuy
|
||||
? AppColorScheme.darkOnTertiary
|
||||
: Colors.white)
|
||||
: colorScheme.onSurface.withOpacity(0.3),
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'${isBuy ? '买入' : '卖出'} ${coinCode ?? ""}',
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isBuy ? Colors.black87 : Colors.white,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 金额输入框(含超额提示)
|
||||
class _AmountInput extends StatefulWidget {
|
||||
final TextEditingController amountController;
|
||||
final String maxAmount;
|
||||
final bool isBuy;
|
||||
final Color actionColor;
|
||||
final VoidCallback onChanged;
|
||||
|
||||
const _AmountInput({
|
||||
required this.amountController,
|
||||
required this.maxAmount,
|
||||
required this.isBuy,
|
||||
required this.actionColor,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_AmountInput> createState() => _AmountInputState();
|
||||
}
|
||||
|
||||
class _AmountInputState extends State<_AmountInput> {
|
||||
bool _isExceeded = false;
|
||||
|
||||
void _checkLimit() {
|
||||
final input = double.tryParse(widget.amountController.text) ?? 0;
|
||||
final max = double.tryParse(widget.maxAmount) ?? 0;
|
||||
final exceeded = widget.isBuy && input > max && max > 0 && input > 0;
|
||||
if (exceeded != _isExceeded) {
|
||||
setState(() => _isExceeded = exceeded);
|
||||
}
|
||||
widget.onChanged();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.amountController.addListener(_checkLimit);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.amountController.removeListener(_checkLimit);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final warningColor = AppColorScheme.warning;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(
|
||||
color: _isExceeded
|
||||
? warningColor.withOpacity(0.5)
|
||||
: widget.actionColor.withOpacity(0.15),
|
||||
),
|
||||
),
|
||||
child: TextField(
|
||||
controller: widget.amountController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
onChanged: (_) => _checkLimit(),
|
||||
style: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: '0.00',
|
||||
hintStyle: TextStyle(
|
||||
color: colorScheme.outlineVariant.withOpacity(0.4)),
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: AppSpacing.md,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: EdgeInsets.only(right: AppSpacing.sm),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm, vertical: AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.actionColor.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text('USDT',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: widget.actionColor.withOpacity(0.7),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
suffixIconConstraints: const BoxConstraints(minWidth: 60),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isExceeded)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: AppSpacing.xs),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 13, color: warningColor),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'超出可用USDT余额',
|
||||
style: TextStyle(fontSize: 11, color: warningColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user