import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import 'package:flutter_animate/flutter_animate.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../data/models/account_models.dart'; import '../../../providers/asset_provider.dart'; import '../../../providers/auth_provider.dart'; import '../../shared/ui_constants.dart'; /// 首页 - "The Kinetic Vault" 设计风格 class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } void _loadData() { final provider = context.read(); provider.loadOverview(); provider.loadTradeAccount(); } @override Widget build(BuildContext context) { super.build(context); return Scaffold( backgroundColor: AppColorScheme.darkBackground, body: Consumer( builder: (context, provider, _) { return RefreshIndicator( onRefresh: () => provider.refreshAll(force: true), color: AppColorScheme.darkPrimary, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(24, 8, 24, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 用户问候 _GreetingSection().animate().fadeIn(duration: 300.ms).slideX(begin: -0.1, end: 0), const SizedBox(height: 16), // 玻璃拟态余额卡片 _GlassBalanceCard(overview: provider.overview) .animate() .fadeIn(duration: 400.ms, delay: 100.ms) .slideY(begin: 0.1, end: 0), const SizedBox(height: 24), // 快捷操作 _QuickActionsGrid( onDeposit: _showDeposit, onWithdraw: _showWithdraw, onTransfer: _showTransfer, ).animate().fadeIn(duration: 500.ms, delay: 200.ms), const SizedBox(height: 32), // 持仓部分 _HoldingsSection(holdings: provider.holdings) .animate() .fadeIn(duration: 500.ms, delay: 300.ms), ], ), ), ); }, ), ); } void _showDeposit() => _showAmountDialog('充值', (amount) { context.read().deposit(amount: amount); }); void _showWithdraw() { final amountController = TextEditingController(); final addressController = TextEditingController(); final contactController = TextEditingController(); final formKey = GlobalKey(); showShadDialog( context: context, builder: (ctx) => ShadDialog( title: const Text('提现'), child: ShadForm( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ ShadInputFormField( id: 'amount', placeholder: const Text('请输入提现金额(USDT)'), controller: amountController, keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), SizedBox(height: AppSpacing.sm + AppSpacing.xs), ShadInputFormField( id: 'address', placeholder: const Text('请输入提现地址'), controller: addressController, validator: (v) => Validators.required(v, '提现地址'), ), SizedBox(height: AppSpacing.sm + AppSpacing.xs), ShadInputFormField( id: 'contact', placeholder: const Text('联系方式(可选)'), controller: contactController, ), ], ), ), actions: [ ShadButton.outline(child: const Text('取消'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('确认'), onPressed: () { if (formKey.currentState!.saveAndValidate()) { Navigator.of(ctx).pop(); context.read().withdraw( amount: amountController.text.trim(), withdrawAddress: addressController.text.trim(), withdrawContact: contactController.text.trim().isEmpty ? null : contactController.text.trim(), ); } }, ), ], ), ); } void _showTransfer() => _showAmountDialog('划转', (amount) { context.read().transfer(direction: 1, amount: amount); }); void _showAmountDialog(String title, void Function(String) onSubmit) { final controller = TextEditingController(); final formKey = GlobalKey(); showShadDialog( context: context, builder: (ctx) => ShadDialog( title: Text(title), child: ShadForm( key: formKey, child: ShadInputFormField( id: 'amount', placeholder: Text('请输入${title}金额(USDT)'), controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), ), actions: [ ShadButton.outline(child: const Text('取消'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('确认'), onPressed: () { if (formKey.currentState!.saveAndValidate()) { onSubmit(controller.text); Navigator.of(ctx).pop(); } }, ), ], ), ); } } /// 用户问候区域 class _GreetingSection extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer( builder: (context, auth, _) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '欢迎回来,', style: TextStyle( color: AppColorScheme.darkOnSurfaceVariant, fontSize: 14, letterSpacing: 0.5, ), ), const SizedBox(height: 4), Text( '${auth.user?.username ?? '用户'}', style: const TextStyle( color: AppColorScheme.darkOnSurface, fontWeight: FontWeight.bold, fontSize: 24, ), ), ], ); }, ); } } /// 玻璃拟态余额卡片 class _GlassBalanceCard extends StatelessWidget { final AssetOverview? overview; const _GlassBalanceCard({required this.overview}); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: AppColorScheme.darkSurfaceBright.withValues(alpha: 0.4), borderRadius: BorderRadius.circular(32), border: Border.all( color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.1), ), ), child: Stack( children: [ // 装饰性发光 - 右上 Positioned( top: -48, right: -48, child: Container( width: 128, height: 128, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColorScheme.darkPrimary.withValues(alpha: 0.2), ), ), ), // 装饰性发光 - 左下 Positioned( bottom: -48, left: -48, child: Container( width: 128, height: 128, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColorScheme.darkSecondary.withValues(alpha: 0.1), ), ), ), // 内容 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 总余额标签 Row( children: [ Text( 'TOTAL BALANCE', style: TextStyle( color: AppColorScheme.darkOnSurfaceVariant, fontSize: 11, letterSpacing: 2, fontWeight: FontWeight.w500, ), ), const SizedBox(width: 8), Icon( LucideIcons.eye, color: AppColorScheme.darkOnSurfaceVariant, size: 14, ), ], ), const SizedBox(height: 8), // 余额数值 Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( overview?.totalAsset ?? '0.00', style: const TextStyle( color: AppColorScheme.darkOnSurface, fontWeight: FontWeight.w900, fontSize: 48, letterSpacing: -2, ), ), const SizedBox(width: 8), Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( 'USDT', style: TextStyle( color: AppColorScheme.darkPrimary, fontWeight: FontWeight.bold, fontSize: 20, ), ), ), ], ), const SizedBox(height: 16), // 今日盈亏 Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppColorScheme.up.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.trendingUp, color: AppColorScheme.up, size: 16, ), const SizedBox(width: 4), Text( '+0.00%', style: TextStyle( color: AppColorScheme.up, fontWeight: FontWeight.bold, fontSize: 14, ), ), const SizedBox(width: 8), Text( "Today's PNL", style: TextStyle( color: AppColorScheme.darkOnSurfaceVariant, fontSize: 12, ), ), ], ), ), ], ), ], ), ); } } /// 快捷操作网格 class _QuickActionsGrid extends StatelessWidget { final VoidCallback onDeposit; final VoidCallback onWithdraw; final VoidCallback onTransfer; const _QuickActionsGrid({ required this.onDeposit, required this.onWithdraw, required this.onTransfer, }); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _QuickActionBtn( icon: LucideIcons.plus, label: '充值', color: AppColorScheme.darkPrimary, onTap: onDeposit, ), _QuickActionBtn( icon: LucideIcons.wallet, label: '提现', color: AppColorScheme.darkSecondary, onTap: onWithdraw, ), _QuickActionBtn( icon: LucideIcons.arrowRightLeft, label: '划转', color: AppColorScheme.up, onTap: onTransfer, ), _QuickActionBtn( icon: LucideIcons.trendingUp, label: '交易', color: AppColorScheme.darkPrimary, onTap: () { // TODO: 实现跳转到交易页面的功能 }, ), ], ); } } /// 快捷操作按钮 class _QuickActionBtn extends StatefulWidget { final IconData icon; final String label; final Color color; final VoidCallback onTap; const _QuickActionBtn({ required this.icon, required this.label, required this.color, required this.onTap, }); @override State<_QuickActionBtn> createState() => _QuickActionBtnState(); } class _QuickActionBtnState extends State<_QuickActionBtn> { bool _isPressed = false; @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, onTapDown: (_) => setState(() => _isPressed = true), onTapUp: (_) => setState(() => _isPressed = false), onTapCancel: () => setState(() => _isPressed = false), child: Column( children: [ AnimatedContainer( duration: const Duration(milliseconds: 200), width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColorScheme.darkSurfaceHigh, border: Border.all( color: widget.color.withValues(alpha: 0.2), ), boxShadow: _isPressed ? [ BoxShadow( color: widget.color.withValues(alpha: 0.4), blurRadius: 20, spreadRadius: 0, ), ] : null, ), child: Icon( widget.icon, color: widget.color, size: 24, ), ), const SizedBox(height: 8), Text( widget.label, style: TextStyle( color: AppColorScheme.darkOnSurfaceVariant, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ), ); } } /// 持仓部分 class _HoldingsSection extends StatelessWidget { final List holdings; const _HoldingsSection({required this.holdings}); @override Widget build(BuildContext context) { return Column( children: [ // 标题行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '我的持仓', style: TextStyle( color: AppColorScheme.darkOnSurface, fontWeight: FontWeight.bold, fontSize: 18, letterSpacing: -0.5, ), ), TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: AppColorScheme.darkPrimary, padding: const EdgeInsets.symmetric(horizontal: 8), ), child: Row( children: [ const Text( 'Assets Detail', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(width: 4), Icon( LucideIcons.chevronRight, size: 16, color: AppColorScheme.darkPrimary, ), ], ), ), ], ), const SizedBox(height: 16), // 持仓内容 holdings.isEmpty ? const _EmptyHoldings() : _HoldingsList(holdings: holdings), ], ); } } /// 空持仓状态 class _EmptyHoldings extends StatelessWidget { const _EmptyHoldings(); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 48, horizontal: 24), decoration: BoxDecoration( color: AppColorScheme.darkSurfaceLow.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(40), border: Border.all( color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.1), ), ), child: Column( children: [ // 3D 钱包图标 SizedBox( width: 128, height: 128, child: Stack( children: [ // 背景模糊效果 Positioned.fill( child: Transform.rotate( angle: 0.2, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), gradient: LinearGradient( colors: [ AppColorScheme.darkPrimary.withValues(alpha: 0.2), AppColorScheme.darkSecondary.withValues(alpha: 0.2), ], ), ), ), ), ), // 主钱包图标 Positioned.fill( child: Center( child: Container( width: 80, height: 80, decoration: BoxDecoration( color: AppColorScheme.darkSurfaceHighest, borderRadius: BorderRadius.circular(24), border: Border.all( color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.2), ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 24, offset: const Offset(0, 8), ), ], ), child: Icon( LucideIcons.wallet, color: AppColorScheme.darkPrimary, size: 40, ), ), ), ), // 代币图标 Positioned( top: 8, right: 16, child: Container( width: 40, height: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColorScheme.up.withValues(alpha: 0.2), border: Border.all( color: AppColorScheme.up.withValues(alpha: 0.3), ), ), child: Icon( LucideIcons.coins, color: AppColorScheme.up, size: 20, ), ), ), ], ), ), const SizedBox(height: 24), const Text( 'No holdings yet.', style: TextStyle( color: AppColorScheme.darkOnSurface, fontWeight: FontWeight.w600, fontSize: 16, ), ), const SizedBox(height: 8), Text( '暂无持仓,快去交易吧~', style: TextStyle( color: AppColorScheme.darkOnSurfaceVariant, fontSize: 14, ), ), const SizedBox(height: 32), // 开始交易按钮 Container( decoration: BoxDecoration( gradient: AppColorScheme.darkCtaGradient, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: AppColorScheme.darkPrimary.withValues(alpha: 0.3), blurRadius: 30, offset: const Offset(0, 10), ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: () {}, borderRadius: BorderRadius.circular(12), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 40, vertical: 16), child: Text( 'Start Trading', style: TextStyle( color: AppColorScheme.darkBackground, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: -0.5, ), ), ), ), ), ), ], ), ); } } /// 持仓列表 class _HoldingsList extends StatelessWidget { final List holdings; const _HoldingsList({required this.holdings}); @override Widget build(BuildContext context) { final displayHoldings = holdings.length > 5 ? holdings.sublist(0, 5) : holdings; return Container( decoration: BoxDecoration( color: AppColorScheme.darkSurface.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(24), border: Border.all( color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.1), ), ), child: ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.all(16), itemCount: displayHoldings.length, separatorBuilder: (_, __) => Divider( color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.1), height: 1, ), itemBuilder: (context, index) { return _HoldingItem(holding: displayHoldings[index]) .animate() .fadeIn(delay: Duration(milliseconds: 50 * index)); }, ), ); } } /// 持仓项 class _HoldingItem extends StatelessWidget { final AccountTrade holding; const _HoldingItem({required this.holding}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ CircleAvatar( radius: 18, backgroundColor: AppColorScheme.darkPrimary.withValues(alpha: 0.1), child: Text( holding.coinCode.substring(0, 1), style: const TextStyle( color: AppColorScheme.darkPrimary, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( holding.coinCode, style: const TextStyle( color: AppColorScheme.darkOnSurface, fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( holding.quantity, style: TextStyle( color: AppColorScheme.darkOnSurfaceVariant, fontSize: 12, ), ), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${holding.currentValue} USDT', style: const TextStyle( color: AppColorScheme.darkOnSurface, fontWeight: FontWeight.w500, fontSize: 14, ), ), Text( holding.formattedProfitRate, style: TextStyle( color: holding.isProfit ? AppColorScheme.up : AppColorScheme.down, fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ], ), ); } }