Files
monisuo/flutter_monisuo/lib/ui/pages/trade/trade_page.dart
sion a65aa0fa86 fix(ui): 修复主题切换功能,支持明暗主题动态切换
- 替换所有硬编码颜色为动态颜色
- 所有页面使用 Theme.of(context) 获取主题颜色
- 支持深色和浅色主题切换
- 修复 GlassPanel 和 NeonGlow 组件的主题适配
- 完善 lightMaterial ColorScheme 定义
- 测试主题切换功能正常

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 02:50:25 +08:00

593 lines
17 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);
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: 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 colorScheme = Theme.of(context).colorScheme;
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 colorScheme = Theme.of(context).colorScheme;
final isBuy = _tradeType == 0;
showShadDialog(
context: context,
builder: (ctx) => ShadDialog.alert(
title: Row(
children: [
NeonIcon(
icon: Icons.check_circle,
color: 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();
},
),
],
),
);
}
}
/// 币种选择器 - 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) {
final colorScheme = Theme.of(context).colorScheme;
// 自动选择第一个币种
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: colorScheme.onSurface,
),
),
SizedBox(height: AppSpacing.xs),
Text(
selectedCoin?.name ?? '点击选择交易对',
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
Icon(
LucideIcons.chevronRight,
color: colorScheme.onSurfaceVariant,
),
],
),
);
}
}
/// 币种头像 - 带霓虹光效
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,
),
),
),
);
}
}
/// 价格卡片 - Glass Panel 风格
class _PriceCard extends StatelessWidget {
final Coin coin;
const _PriceCard({required this.coin});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final color = coin.isUp ? AppColorScheme.up : AppColorScheme.down;
final bgColor = coin.isUp
? AppColorScheme.up.withOpacity(0.1)
: colorScheme.error.withOpacity(0.1);
return GlassCard(
showNeonGlow: false,
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,
),
),
),
],
),
);
}
}
/// 交易表单 - 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) {
final colorScheme = Theme.of(context).colorScheme;
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',
colorScheme: colorScheme,
),
SizedBox(height: AppSpacing.md),
// 数量输入
_buildInputField(
label: '数量',
controller: quantityController,
placeholder: '输入数量',
suffix: selectedCoin?.code ?? '',
colorScheme: colorScheme,
),
SizedBox(height: AppSpacing.lg),
// 信息行
_InfoRow(label: '交易金额', value: '${_calculateAmount()} USDT', colorScheme: colorScheme),
SizedBox(height: AppSpacing.sm),
_InfoRow(label: '可用', value: '${tradeBalance ?? '0.00'} USDT', colorScheme: colorScheme),
],
),
);
}
Widget _buildInputField({
required String label,
required TextEditingController controller,
required String placeholder,
required String suffix,
required ColorScheme colorScheme,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
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: controller,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
style: GoogleFonts.spaceGrotesk(
fontSize: 20,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
decoration: InputDecoration(
hintText: placeholder,
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(
suffix,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: colorScheme.onSurfaceVariant,
),
),
),
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) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: EdgeInsets.all(AppSpacing.xs),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerLowest,
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.withOpacity(0.15) : Colors.transparent,
borderRadius: BorderRadius.circular(AppRadius.md),
border: isSelected ? null : Border.all(color: color.withOpacity(0.3)),
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isSelected ? color : color.withOpacity(0.7),
fontWeight: FontWeight.w700,
fontSize: 14,
letterSpacing: 0.5,
),
),
),
),
);
}
}
/// 信息行
class _InfoRow extends StatelessWidget {
final String label;
final String value;
final ColorScheme colorScheme;
const _InfoRow({required this.label, required this.value, required this.colorScheme});
@override
Widget build(BuildContext context) {
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: colorScheme.onSurface,
),
),
],
);
}
}
/// 交易按钮 - 带霓虹光效
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,
);
}
}