Files
monisuo/flutter_monisuo/lib/ui/pages/trade/trade_page.dart
sion df0e8beba9 feat(ui): 应用新设计系统到 Flutter 项目
- 更新颜色系统为 Material Design 3
  * Primary: #72dcff (青色)
  * Secondary: #dd8bfb (紫色)
  * Tertiary: #afffd1 (绿色)

- 创建新的 UI 组件
  * GlassPanel: 毛玻璃效果面板
  * NeonGlow: 霓虹光效组件
  * GradientButton: 渐变按钮组件

- 更新所有页面样式
  * 交易页面 (trade_page.dart)
  * 行情页面 (market_page.dart)
  * 资产页面 (asset_page.dart)
  * 我的页面 (mine_page.dart)
  * 订单页面 (orders_page.dart)

- 支持深色和浅色主题
- 所有 UI 文字使用中文
- 保持现有 API 接口不变

变更统计:
- 9 个文件修改
- 1,893 行新增
- 691 行删除
- 3 个新组件
2026-03-24 02:16:19 +08:00

577 lines
16 KiB
Dart

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 '../../shared/ui_constants.dart';
import '../../components/glass_panel.dart';
import '../../components/neon_glow.dart';
/// 交易页面 - Material Design 3 风格
class TradePage extends StatefulWidget {
const TradePage({super.key});
@override
State<TradePage> createState() => _TradePageState();
}
class _TradePageState extends State<TradePage> with AutomaticKeepAliveClientMixin {
int _tradeType = 0; // 0=买入, 1=卖出
Coin? _selectedCoin;
final _formKey = GlobalKey<ShadFormState>();
final _priceController = TextEditingController();
final _quantityController = TextEditingController();
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}
void _loadData() {
context.read<MarketProvider>().loadCoins();
}
@override
void dispose() {
_priceController.dispose();
_quantityController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: AppColorScheme.darkBackground,
body: Consumer2<MarketProvider, AssetProvider>(
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.lg),
_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 isBuy = _tradeType == 0;
showShadDialog(
context: context,
builder: (ctx) => ShadDialog.alert(
title: Row(
children: [
NeonIcon(
icon: Icons.check_circle,
color: AppColorScheme.darkPrimary,
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();
},
),
],
),
);
}
}
/// 币种选择器 - Glass Panel 风格
class _CoinSelector extends StatelessWidget {
final Coin? selectedCoin;
final List<Coin> coins;
final ValueChanged<Coin> onCoinLoaded;
const _CoinSelector({
required this.selectedCoin,
required this.coins,
required this.onCoinLoaded,
});
@override
Widget build(BuildContext context) {
// 自动选择第一个币种
if (selectedCoin == null && coins.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) => onCoinLoaded(coins.first));
}
return GlassCard(
showNeonGlow: false,
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: AppColorScheme.darkOnSurface,
),
),
SizedBox(height: AppSpacing.xs),
Text(
selectedCoin?.name ?? '点击选择交易对',
style: TextStyle(
fontSize: 12,
color: AppColorScheme.darkOnSurfaceVariant,
),
),
],
),
),
Icon(
LucideIcons.chevronRight,
color: AppColorScheme.darkOnSurfaceVariant,
),
],
),
);
}
}
/// 币种头像 - 带霓虹光效
class _CoinAvatar extends StatelessWidget {
final String? icon;
const _CoinAvatar({this.icon});
@override
Widget build(BuildContext context) {
return Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppColorScheme.darkPrimary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: AppColorScheme.darkPrimary.withValues(alpha: 0.2),
),
),
child: Center(
child: Text(
icon ?? '?',
style: TextStyle(
fontSize: 20,
color: AppColorScheme.darkPrimary,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
/// 价格卡片 - Glass Panel 风格
class _PriceCard extends StatelessWidget {
final Coin coin;
const _PriceCard({required this.coin});
@override
Widget build(BuildContext context) {
final color = coin.isUp ? AppColorScheme.up : AppColorScheme.down;
final bgColor = coin.isUp
? AppColorScheme.darkTertiary.withValues(alpha: 0.1)
: AppColorScheme.darkError.withValues(alpha: 0.1);
return GlassCard(
showNeonGlow: false,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'最新价',
style: TextStyle(
fontSize: 12,
color: AppColorScheme.darkOnSurfaceVariant,
),
),
SizedBox(height: AppSpacing.xs),
Text(
'\$${coin.formattedPrice}',
style: GoogleFonts.spaceGrotesk(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColorScheme.darkOnSurface,
),
),
],
),
Container(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(AppRadius.md),
border: Border.all(
color: color.withValues(alpha: 0.2),
),
),
child: Text(
coin.formattedChange,
style: TextStyle(
fontSize: 16,
color: color,
fontWeight: FontWeight.w700,
),
),
),
],
),
);
}
}
/// 交易表单 - Glass Panel 风格
class _TradeForm extends StatelessWidget {
final int tradeType;
final Coin? selectedCoin;
final TextEditingController priceController;
final TextEditingController quantityController;
final String? tradeBalance;
final ValueChanged<int> 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) {
return GlassPanel(
padding: EdgeInsets.all(AppSpacing.lg),
child: Column(
children: [
// 买入/卖出切换
_TradeTypeSelector(
tradeType: tradeType,
onChanged: onTradeTypeChanged,
),
SizedBox(height: AppSpacing.lg),
// 价格输入
_buildInputField(
label: '价格(USDT)',
controller: priceController,
placeholder: '输入价格',
suffix: 'USDT',
),
SizedBox(height: AppSpacing.md),
// 数量输入
_buildInputField(
label: '数量',
controller: quantityController,
placeholder: '输入数量',
suffix: selectedCoin?.code ?? '',
),
SizedBox(height: AppSpacing.lg),
// 信息行
_InfoRow(label: '交易金额', value: '${_calculateAmount()} USDT'),
SizedBox(height: AppSpacing.sm),
_InfoRow(label: '可用', value: '${tradeBalance ?? '0.00'} USDT'),
],
),
);
}
Widget _buildInputField({
required String label,
required TextEditingController controller,
required String placeholder,
required String suffix,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
color: AppColorScheme.darkOnSurfaceVariant,
),
),
SizedBox(height: AppSpacing.xs),
Container(
decoration: BoxDecoration(
color: AppColorScheme.darkSurfaceLowest,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.3),
),
),
child: TextField(
controller: controller,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColorScheme.darkOnSurface,
),
decoration: InputDecoration(
hintText: placeholder,
hintStyle: TextStyle(
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.5),
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: AppSpacing.md,
vertical: AppSpacing.md,
),
suffixIcon: Padding(
padding: EdgeInsets.only(right: AppSpacing.sm),
child: Text(
suffix,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppColorScheme.darkOnSurfaceVariant,
),
),
),
suffixIconConstraints: const BoxConstraints(minWidth: 50),
),
),
),
],
);
}
String _calculateAmount() {
final price = double.tryParse(priceController.text) ?? 0;
final quantity = double.tryParse(quantityController.text) ?? 0;
return (price * quantity).toStringAsFixed(2);
}
}
/// 交易类型选择器 - Material Design 3 风格
class _TradeTypeSelector extends StatelessWidget {
final int tradeType;
final ValueChanged<int> onChanged;
const _TradeTypeSelector({required this.tradeType, required this.onChanged});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(AppSpacing.xs),
decoration: BoxDecoration(
color: AppColorScheme.darkSurfaceLowest,
borderRadius: BorderRadius.circular(AppRadius.xl),
),
child: Row(
children: [
Expanded(
child: _TypeButton(
label: 'Buy',
isSelected: tradeType == 0,
color: AppColorScheme.up,
onTap: () => onChanged(0),
),
),
SizedBox(width: AppSpacing.sm),
Expanded(
child: _TypeButton(
label: 'Sell',
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: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs),
decoration: BoxDecoration(
color: isSelected ? color.withValues(alpha: 0.15) : Colors.transparent,
borderRadius: BorderRadius.circular(AppRadius.md),
border: isSelected ? null : Border.all(color: color.withValues(alpha: 0.3)),
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isSelected ? color : color.withValues(alpha: 0.7),
fontWeight: FontWeight.w700,
fontSize: 14,
letterSpacing: 0.5,
),
),
),
),
);
}
}
/// 信息行
class _InfoRow extends StatelessWidget {
final String label;
final String value;
const _InfoRow({required this.label, required this.value});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 14,
color: AppColorScheme.darkOnSurfaceVariant,
),
),
Text(
value,
style: GoogleFonts.spaceGrotesk(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColorScheme.darkOnSurface,
),
),
],
);
}
}
/// 交易按钮 - 带霓虹光效
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) {
return NeonButton(
text: '${isBuy ? '买入' : '卖出'} ${coinCode ?? ''}',
type: isBuy ? NeonButtonType.tertiary : NeonButtonType.error,
icon: isBuy ? Icons.arrow_downward : Icons.arrow_upward,
onPressed: onPressed,
width: double.infinity,
showGlow: true,
);
}
}