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); final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.background, body: Consumer( builder: (context, provider, _) { return RefreshIndicator( onRefresh: () => provider.refreshAll(force: true), color: colorScheme.primary, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.only( left: AppSpacing.lg, right: AppSpacing.lg, top: AppSpacing.sm, bottom: 100, // 底部留出导航栏空间 ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 用户问候 _GreetingSection().animate().fadeIn(duration: 300.ms).slideX(begin: -0.1, end: 0), SizedBox(height: AppSpacing.md), // 玻璃拟态余额卡片 _GlassBalanceCard(overview: provider.overview) .animate() .fadeIn(duration: 400.ms, delay: 100.ms) .slideY(begin: 0.1, end: 0), SizedBox(height: AppSpacing.lg), // 快捷操作 _QuickActionsGrid( onDeposit: _showDeposit, onWithdraw: _showWithdraw, onTransfer: _showTransfer, ).animate().fadeIn(duration: 500.ms, delay: 200.ms), SizedBox(height: AppSpacing.xl), // 持仓部分 _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) { final colorScheme = Theme.of(context).colorScheme; return Consumer( builder: (context, auth, _) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '欢迎回来,', style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 14, letterSpacing: 0.5, ), ), SizedBox(height: AppSpacing.xs), Text( '${auth.user?.username ?? '用户'}', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 24, ), ), ], ); }, ); } } /// 玻璃拟态余额卡片 class _GlassBalanceCard extends StatelessWidget { final AssetOverview? overview; const _GlassBalanceCard({required this.overview}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( width: double.infinity, padding: EdgeInsets.all(AppSpacing.xl), decoration: BoxDecoration( color: colorScheme.surfaceBright.withOpacity(isDark ? 0.4 : 0.6), borderRadius: BorderRadius.circular(AppRadius.xxl + AppSpacing.sm), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.1), ), ), child: Stack( children: [ // 装饰性发光 - 右上 Positioned( top: -AppSpacing.xxl, right: -AppSpacing.xxl, child: Container( width: 128, // 装饰性元素保持固定尺寸 height: 128, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.primary.withOpacity(isDark ? 0.2 : 0.1), ), ), ), // 装饰性发光 - 左下 Positioned( bottom: -AppSpacing.xxl, left: -AppSpacing.xxl, child: Container( width: 128, // 装饰性元素保持固定尺寸 height: 128, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.secondary.withOpacity(isDark ? 0.1 : 0.05), ), ), ), // 内容 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 总余额标签 Row( children: [ Text( '总资产', style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 11, letterSpacing: 2, fontWeight: FontWeight.w500, ), ), const SizedBox(width: 8), Icon( LucideIcons.eye, color: colorScheme.onSurfaceVariant, size: 14, ), ], ), SizedBox(height: AppSpacing.sm), // 余额数值 Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( overview?.totalAsset ?? '0.00', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.w900, fontSize: 48, letterSpacing: -2, ), ), const SizedBox(width: 8), Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( 'USDT', style: TextStyle( color: colorScheme.primary, fontWeight: FontWeight.bold, fontSize: 20, ), ), ), ], ), SizedBox(height: AppSpacing.md), // 今日盈亏 Container( padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs), decoration: BoxDecoration( color: AppColorScheme.getUpBackgroundColor(isDark, opacity: 0.1), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.trendingUp, color: AppColorScheme.getUpColor(isDark), size: 16, ), SizedBox(width: AppSpacing.xs), Text( '+0.00%', style: TextStyle( color: AppColorScheme.getUpColor(isDark), fontWeight: FontWeight.bold, fontSize: 14, ), ), SizedBox(width: AppSpacing.sm), Text( "Today's PNL", style: TextStyle( color: colorScheme.onSurfaceVariant, 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) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _QuickActionBtn( icon: LucideIcons.plus, label: '充值', color: colorScheme.primary, onTap: onDeposit, ), _QuickActionBtn( icon: LucideIcons.wallet, label: '提现', color: colorScheme.secondary, onTap: onWithdraw, ), _QuickActionBtn( icon: LucideIcons.arrowRightLeft, label: '划转', color: AppColorScheme.getUpColor(isDark), onTap: onTransfer, ), _QuickActionBtn( icon: LucideIcons.trendingUp, label: '交易', color: colorScheme.primary, 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) { final colorScheme = Theme.of(context).colorScheme; 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: colorScheme.surfaceContainerHigh, border: Border.all( color: widget.color.withOpacity(0.2), ), boxShadow: _isPressed ? [ BoxShadow( color: widget.color.withOpacity(0.4), blurRadius: 20, spreadRadius: 0, ), ] : null, ), child: Icon( widget.icon, color: widget.color, size: 24, ), ), SizedBox(height: AppSpacing.sm), Text( widget.label, style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ), ); } } /// 持仓部分 class _HoldingsSection extends StatelessWidget { final List holdings; const _HoldingsSection({required this.holdings}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Column( children: [ // 标题行 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '我的持仓', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 18, letterSpacing: -0.5, ), ), TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: colorScheme.primary, padding: const EdgeInsets.symmetric(horizontal: 8), ), child: Row( children: [ Text( '资产详情', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(width: 4), Icon( LucideIcons.chevronRight, size: 16, color: colorScheme.primary, ), ], ), ), ], ), SizedBox(height: AppSpacing.md), // 持仓内容 holdings.isEmpty ? const _EmptyHoldings() : _HoldingsList(holdings: holdings), ], ); } } /// 空持仓状态 class _EmptyHoldings extends StatelessWidget { const _EmptyHoldings(); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( width: double.infinity, padding: EdgeInsets.symmetric(vertical: AppSpacing.xxl, horizontal: AppSpacing.lg), decoration: BoxDecoration( color: colorScheme.surfaceContainerLow.withOpacity(0.5), borderRadius: BorderRadius.circular(AppRadius.xxl + AppSpacing.xl), border: Border.all( color: colorScheme.outlineVariant.withOpacity(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(AppRadius.xxl), gradient: LinearGradient( colors: [ colorScheme.primary.withOpacity(isDark ? 0.2 : 0.1), colorScheme.secondary.withOpacity(isDark ? 0.2 : 0.1), ], ), ), ), ), ), // 主钱包图标 Positioned.fill( child: Center( child: Container( width: 80, // 图标容器保持固定尺寸 height: 80, decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(AppRadius.xxl), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.2), ), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(isDark ? 0.3 : 0.1), blurRadius: AppSpacing.lg, offset: Offset(0, AppSpacing.sm), ), ], ), child: Icon( LucideIcons.wallet, color: colorScheme.primary, size: 40, ), ), ), ), // 代币图标 Positioned( top: AppSpacing.sm, right: AppSpacing.md, child: Container( width: 40, // 小图标保持固定尺寸 height: 40, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColorScheme.getUpBackgroundColor(isDark, opacity: 0.2), border: Border.all( color: AppColorScheme.getUpBackgroundColor(isDark, opacity: 0.3), ), ), child: Icon( LucideIcons.coins, color: AppColorScheme.getUpColor(isDark), size: 20, ), ), ), ], ), ), SizedBox(height: AppSpacing.lg), Text( 'No holdings yet.', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.w600, fontSize: 16, ), ), SizedBox(height: AppSpacing.sm), Text( '暂无持仓,快去交易吧~', style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 14, ), ), SizedBox(height: AppSpacing.xl), // 开始交易按钮 Container( decoration: BoxDecoration( gradient: isDark ? AppColorScheme.darkCtaGradient : AppColorScheme.lightCtaGradient, borderRadius: BorderRadius.circular(AppRadius.lg), boxShadow: [ BoxShadow( color: colorScheme.primary.withOpacity(isDark ? 0.3 : 0.2), blurRadius: 30, // 大模糊半径保持固定 offset: const Offset(0, 10), ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: () {}, borderRadius: BorderRadius.circular(AppRadius.lg), child: Padding( padding: EdgeInsets.symmetric(horizontal: 40, vertical: AppSpacing.md), child: Text( 'Start Trading', style: TextStyle( color: isDark ? colorScheme.background : const Color(0xFFFFFFFF), 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 colorScheme = Theme.of(context).colorScheme; final displayHoldings = holdings.length > 5 ? holdings.sublist(0, 5) : holdings; return Container( decoration: BoxDecoration( color: colorScheme.surface.withOpacity(0.5), borderRadius: BorderRadius.circular(AppRadius.xxl), border: Border.all( color: colorScheme.outlineVariant.withOpacity(0.1), ), ), child: ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.all(AppSpacing.md), itemCount: displayHoldings.length, separatorBuilder: (_, __) => Divider( color: colorScheme.outlineVariant.withOpacity(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) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Padding( padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ CircleAvatar( radius: 18, // 头像半径保持固定 backgroundColor: colorScheme.primary.withOpacity(0.1), child: Text( holding.coinCode.substring(0, 1), style: TextStyle( color: colorScheme.primary, fontWeight: FontWeight.bold, ), ), ), SizedBox(width: AppSpacing.sm + AppSpacing.xs), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( holding.coinCode, style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( holding.quantity, style: TextStyle( color: colorScheme.onSurfaceVariant, fontSize: 12, ), ), ], ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${holding.currentValue} USDT', style: TextStyle( color: colorScheme.onSurface, fontWeight: FontWeight.w500, fontSize: 14, ), ), Text( holding.formattedProfitRate, style: TextStyle( color: holding.isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down, fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ], ), ); } }