From 019374df36805ec1ead1fd5f0bc25b052e60792a Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Mon, 23 Mar 2026 23:19:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/monisuo-dev/SKILL.md | 1 - .../lib/core/theme/app_color_scheme.dart | 1 + .../lib/ui/pages/home/home_page.dart | 752 ++++++++++++------ .../lib/ui/pages/main/main_page.dart | 241 ++++-- 4 files changed, 719 insertions(+), 276 deletions(-) diff --git a/.agents/skills/monisuo-dev/SKILL.md b/.agents/skills/monisuo-dev/SKILL.md index ca307f2..7effc9d 100644 --- a/.agents/skills/monisuo-dev/SKILL.md +++ b/.agents/skills/monisuo-dev/SKILL.md @@ -1,7 +1,6 @@ --- name: monisuo-dev description: Monisuo 项目结构化开发流程技能。用于需求分析、模块化开发、测试验证、构建部署的完整工作流。 -version: 1.0.0 --- # Monisuo 开发技能 diff --git a/flutter_monisuo/lib/core/theme/app_color_scheme.dart b/flutter_monisuo/lib/core/theme/app_color_scheme.dart index 2bc62eb..b4c1b28 100644 --- a/flutter_monisuo/lib/core/theme/app_color_scheme.dart +++ b/flutter_monisuo/lib/core/theme/app_color_scheme.dart @@ -26,6 +26,7 @@ class AppColorScheme { static const Color darkSurface = Color(0xFF151921); static const Color darkSurfaceHigh = Color(0xFF1a1f2a); static const Color darkSurfaceHighest = Color(0xFF22262f); + static const Color darkSurfaceBright = Color(0xFF282c36); /// Ghost Border - 后备边框 static const Color darkOutlineVariant = Color(0xFF45484f); diff --git a/flutter_monisuo/lib/ui/pages/home/home_page.dart b/flutter_monisuo/lib/ui/pages/home/home_page.dart index e819594..fc82f8f 100644 --- a/flutter_monisuo/lib/ui/pages/home/home_page.dart +++ b/flutter_monisuo/lib/ui/pages/home/home_page.dart @@ -1,14 +1,15 @@ +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'; -/// 首页 - 使用 shadcn_ui 现代化设计 +/// 首页 - "The Kinetic Vault" 设计风格 class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -35,34 +36,40 @@ class _HomePageState extends State with AutomaticKeepAliveClientMixin @override Widget build(BuildContext context) { super.build(context); - final theme = ShadTheme.of(context); return Scaffold( - backgroundColor: theme.colorScheme.background, + backgroundColor: AppColorScheme.darkBackground, body: Consumer( builder: (context, provider, _) { return RefreshIndicator( onRefresh: () => provider.refreshAll(force: true), - color: theme.colorScheme.primary, + color: AppColorScheme.darkPrimary, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - padding: AppSpacing.pagePadding, + padding: const EdgeInsets.fromLTRB(24, 8, 24, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: AppSpacing.sm), - _Header(), - SizedBox(height: AppSpacing.lg + AppSpacing.sm), - _AssetOverviewCard(overview: provider.overview), - SizedBox(height: AppSpacing.md), - _QuickActions( + // 用户问候 + _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, - onTrade: _navigateToTrade, - ), - SizedBox(height: AppSpacing.lg), - _HoldingsList(holdings: provider.holdings), + ).animate().fadeIn(duration: 500.ms, delay: 200.ms), + const SizedBox(height: 32), + // 持仓部分 + _HoldingsSection(holdings: provider.holdings) + .animate() + .fadeIn(duration: 500.ms, delay: 300.ms), ], ), ), @@ -138,7 +145,7 @@ class _HomePageState extends State with AutomaticKeepAliveClientMixin context.read().transfer(direction: 1, amount: amount); }); - void _showAmountDialog(String title, Function(String) onSubmit) { + void _showAmountDialog(String title, void Function(String) onSubmit) { final controller = TextEditingController(); final formKey = GlobalKey(); @@ -171,187 +178,500 @@ class _HomePageState extends State with AutomaticKeepAliveClientMixin ), ); } - - void _navigateToTrade() { - // 切换到交易页 - 通过 MainController - } } -/// 头部组件 -class _Header extends StatelessWidget { +/// 用户问候区域 +class _GreetingSection extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - return Consumer( builder: (context, auth, _) { - final user = auth.user; - return Row( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - _Avatar(text: user?.avatarText), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '你好,${user?.username ?? '用户'}', - style: theme.textTheme.large.copyWith(fontWeight: FontWeight.bold), - ), - SizedBox(height: AppSpacing.xs), - Text('欢迎来到模拟所', style: theme.textTheme.muted), - ], + Text( + 'Welcome back,', + style: TextStyle( + color: AppColorScheme.darkOnSurfaceVariant, + fontSize: 14, + letterSpacing: 0.5, + ), + ), + const SizedBox(height: 4), + Text( + 'Hello, ${auth.user?.username ?? 'User'}', + style: const TextStyle( + color: AppColorScheme.darkOnSurface, + fontWeight: FontWeight.bold, + fontSize: 24, ), ), ], - ).animate().fadeIn(duration: 300.ms).slideX(begin: -0.1, end: 0); + ); }, ); } } -/// 头像组件 -class _Avatar extends StatelessWidget { - final String? text; +/// 玻璃拟态余额卡片 +class _GlassBalanceCard extends StatelessWidget { + final AssetOverview? overview; - const _Avatar({this.text}); + const _GlassBalanceCard({required this.overview}); @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - - return CircleAvatar( - radius: 20, - backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.2), - child: Text( - text ?? 'U', - style: TextStyle(color: theme.colorScheme.primary, fontWeight: FontWeight.bold), + 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 _AssetOverviewCard extends StatelessWidget { - final dynamic overview; +/// 快捷操作网格 +class _QuickActionsGrid extends StatelessWidget { + final VoidCallback onDeposit; + final VoidCallback onWithdraw; + final VoidCallback onTransfer; - const _AssetOverviewCard({required this.overview}); + const _QuickActionsGrid({ + required this.onDeposit, + required this.onWithdraw, + required this.onTransfer, + }); @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - - return Container( - width: double.infinity, - padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.sm), - decoration: BoxDecoration( - gradient: AppColorScheme.assetCardGradient, - borderRadius: BorderRadius.circular(AppRadius.xl), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('总资产(USDT)', style: theme.textTheme.small.copyWith(color: Colors.white70)), - SizedBox(height: AppSpacing.sm), - Text( - overview?.totalAsset ?? '0.00', - style: theme.textTheme.h2.copyWith(color: Colors.white, fontWeight: FontWeight.bold), - ), - SizedBox(height: AppSpacing.lg + AppSpacing.sm), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _AssetItem(label: '资金账户', value: overview?.fundBalance ?? '0.00'), - _AssetItem(label: '交易账户', value: overview?.tradeBalance ?? '0.00'), - ], - ), - ], - ), - ).animate().fadeIn(duration: 400.ms).slideY(begin: 0.1, end: 0); - } -} - -/// 资产项 -class _AssetItem extends StatelessWidget { - final String label; - final String value; - - const _AssetItem({required this.label, required this.value}); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Text(label, style: const TextStyle(fontSize: 12, color: Colors.white70)), - SizedBox(height: AppSpacing.xs), - Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white)), + _QuickActionBtn( + icon: LucideIcons.plusCircle, + 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.lineChart, + label: '交易', + color: AppColorScheme.darkPrimary, + onTap: () { + // TODO: 实现跳转到交易页面的功能 + }, + ), ], ); } } -/// 快捷操作 -class _QuickActions extends StatelessWidget { - final VoidCallback onDeposit; - final VoidCallback onWithdraw; - final VoidCallback onTransfer; - final VoidCallback onTrade; - - const _QuickActions({ - required this.onDeposit, - required this.onWithdraw, - required this.onTransfer, - required this.onTrade, - }); - - @override - Widget build(BuildContext context) { - return ShadCard( - padding: AppSpacing.cardPadding, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _ActionButton(icon: LucideIcons.arrowDownToLine, text: '充值', color: AppColorScheme.success, onTap: onDeposit), - _ActionButton(icon: LucideIcons.arrowUpFromLine, text: '提现', color: AppColorScheme.warning, onTap: onWithdraw), - _ActionButton(icon: LucideIcons.arrowRightLeft, text: '划转', color: AppColorScheme.info, onTap: onTransfer), - _ActionButton(icon: LucideIcons.trendingUp, text: '交易', color: AppColorScheme.info, onTap: onTrade), - ], - ), - ).animate().fadeIn(duration: 500.ms, delay: 100.ms); - } -} - -/// 操作按钮 -class _ActionButton extends StatelessWidget { +/// 快捷操作按钮 +class _QuickActionBtn extends StatefulWidget { final IconData icon; - final String text; + final String label; final Color color; final VoidCallback onTap; - const _ActionButton({ + const _QuickActionBtn({ required this.icon, - required this.text, + required this.label, required this.color, required this.onTap, }); @override - Widget build(BuildContext context) { - final theme = ShadTheme.of(context); + State<_QuickActionBtn> createState() => _QuickActionBtnState(); +} +class _QuickActionBtnState extends State<_QuickActionBtn> { + bool _isPressed = false; + + @override + Widget build(BuildContext context) { return GestureDetector( - onTap: onTap, + onTap: widget.onTap, + onTapDown: (_) => setState(() => _isPressed = true), + onTapUp: (_) => setState(() => _isPressed = false), + onTapCancel: () => setState(() => _isPressed = false), child: Column( children: [ - Container( - width: 48, - height: 48, - decoration: BoxDecoration(color: color.withValues(alpha: 0.15), shape: BoxShape.circle), - child: Icon(icon, color: color, size: 22), + 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, + ), + ), + ), + ), + ), ), - SizedBox(height: AppSpacing.sm), - Text(text, style: TextStyle(fontSize: 12, color: theme.colorScheme.foreground)), ], ), ); @@ -360,97 +680,51 @@ class _ActionButton extends StatelessWidget { /// 持仓列表 class _HoldingsList extends StatelessWidget { - final List holdings; + final List holdings; const _HoldingsList({required this.holdings}); @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - - return ShadCard( - padding: AppSpacing.cardPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('我的持仓', style: theme.textTheme.large.copyWith(fontWeight: FontWeight.bold)), - Icon(LucideIcons.chevronRight, color: theme.colorScheme.mutedForeground, size: 20), - ], - ), - SizedBox(height: AppSpacing.md), - if (holdings.isEmpty) - _EmptyHoldings() - else - _HoldingsListView(holdings: holdings), - ], - ), - ).animate().fadeIn(duration: 500.ms, delay: 200.ms); - } -} - -/// 空持仓提示 -class _EmptyHoldings extends StatelessWidget { - @override - Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - - return Center( - child: Padding( - padding: EdgeInsets.all(AppSpacing.xl), - child: Column( - children: [ - Icon(LucideIcons.wallet, size: 48, color: theme.colorScheme.mutedForeground), - SizedBox(height: AppSpacing.sm + AppSpacing.xs), - Text('暂无持仓', style: theme.textTheme.muted), - SizedBox(height: AppSpacing.xs), - Text('快去交易吧~', style: theme.textTheme.muted.copyWith(fontSize: 12)), - ], - ), - ), - ); - } -} - -/// 持仓列表视图 -class _HoldingsListView extends StatelessWidget { - final List holdings; - - const _HoldingsListView({required this.holdings}); - - @override - Widget build(BuildContext context) { - final theme = ShadTheme.of(context); final displayHoldings = holdings.length > 5 ? holdings.sublist(0, 5) : holdings; - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: displayHoldings.length, - separatorBuilder: (_, __) => Divider(color: theme.colorScheme.border, height: 1), - itemBuilder: (context, index) { - return _HoldingItem(holding: displayHoldings[index]) - .animate() - .fadeIn(delay: Duration(milliseconds: 50 * index)); - }, + 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 dynamic holding; + final AccountTrade holding; const _HoldingItem({required this.holding}); @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - return Padding( - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm), + padding: const EdgeInsets.symmetric(vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -458,18 +732,34 @@ class _HoldingItem extends StatelessWidget { children: [ CircleAvatar( radius: 18, - backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.1), + backgroundColor: AppColorScheme.darkPrimary.withValues(alpha: 0.1), child: Text( holding.coinCode.substring(0, 1), - style: TextStyle(color: theme.colorScheme.primary, fontWeight: FontWeight.bold), + style: const TextStyle( + color: AppColorScheme.darkPrimary, + fontWeight: FontWeight.bold, + ), ), ), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), + const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(holding.coinCode, style: theme.textTheme.large.copyWith(fontWeight: FontWeight.bold)), - Text(holding.quantity, style: theme.textTheme.muted), + Text( + holding.coinCode, + style: const TextStyle( + color: AppColorScheme.darkOnSurface, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + Text( + holding.quantity, + style: TextStyle( + color: AppColorScheme.darkOnSurfaceVariant, + fontSize: 12, + ), + ), ], ), ], @@ -477,12 +767,20 @@ class _HoldingItem extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('${holding.currentValue} USDT', style: theme.textTheme.small.copyWith(fontWeight: FontWeight.w500)), + 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, ), ), ], diff --git a/flutter_monisuo/lib/ui/pages/main/main_page.dart b/flutter_monisuo/lib/ui/pages/main/main_page.dart index 12f94c9..2d02f4e 100644 --- a/flutter_monisuo/lib/ui/pages/main/main_page.dart +++ b/flutter_monisuo/lib/ui/pages/main/main_page.dart @@ -1,5 +1,9 @@ +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; +import 'package:provider/provider.dart'; +import '../../../core/theme/app_color_scheme.dart'; +import '../../../providers/auth_provider.dart'; import '../home/home_page.dart'; import '../market/market_page.dart'; import '../trade/trade_page.dart'; @@ -15,7 +19,7 @@ class _NavItem { const _NavItem({required this.label, required this.icon, required this.page}); } -/// 主页面(使用 shadcn_ui 风格) +/// 主页面 - "The Kinetic Vault" 设计风格 class MainPage extends StatefulWidget { const MainPage({super.key}); @@ -27,12 +31,12 @@ class _MainPageState extends State { int _currentIndex = 0; final Set _loadedPages = {0}; - static const _navItems = [ - _NavItem(label: '首页', icon: LucideIcons.house, page: HomePage()), - _NavItem(label: '行情', icon: LucideIcons.trendingUp, page: MarketPage()), - _NavItem(label: '交易', icon: LucideIcons.arrowLeftRight, page: TradePage()), - _NavItem(label: '资产', icon: LucideIcons.wallet, page: AssetPage()), - _NavItem(label: '我的', icon: LucideIcons.user, page: MinePage()), + static final _navItems = [ + _NavItem(label: '首页', icon: LucideIcons.house, page: const HomePage()), + _NavItem(label: '行情', icon: LucideIcons.trendingUp, page: const MarketPage()), + _NavItem(label: '交易', icon: LucideIcons.arrowLeftRight, page: const TradePage()), + _NavItem(label: '资产', icon: LucideIcons.wallet, page: const AssetPage()), + _NavItem(label: '我的', icon: LucideIcons.user, page: const MinePage()), ]; void _onTabChanged(int index) { @@ -44,13 +48,21 @@ class _MainPageState extends State { @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - return Scaffold( - body: LazyIndexedStack( - index: _currentIndex, - loadedIndexes: _loadedPages, - children: _navItems.map((item) => item.page).toList(), + backgroundColor: AppColorScheme.darkBackground, + body: Column( + children: [ + // 公共顶部导航栏 + const _TopAppBar(), + // 页面内容 + Expanded( + child: LazyIndexedStack( + index: _currentIndex, + loadedIndexes: _loadedPages, + children: _navItems.map((item) => item.page).toList(), + ), + ), + ], ), bottomNavigationBar: _BottomNavBar( items: _navItems, @@ -61,7 +73,121 @@ class _MainPageState extends State { } } -/// 底部导航栏 +/// 顶部导航栏 - 玻璃拟态效果 +class _TopAppBar extends StatelessWidget { + const _TopAppBar(); + + @override + Widget build(BuildContext context) { + return Container( + height: 64, + decoration: BoxDecoration( + color: AppColorScheme.darkSurfaceBright.withValues(alpha: 0.4), + boxShadow: [ + BoxShadow( + color: AppColorScheme.darkPrimary.withValues(alpha: 0.06), + blurRadius: 64, + offset: const Offset(0, 32), + ), + ], + ), + child: ClipRRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 24, sigmaY: 24), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 左侧头像和标题 + Consumer( + builder: (context, auth, _) { + return Row( + children: [ + // 头像 + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.2), + ), + ), + child: CircleAvatar( + backgroundColor: AppColorScheme.darkSurfaceHigh, + child: Text( + auth.user?.avatarText ?? 'U', + style: const TextStyle( + color: AppColorScheme.darkPrimary, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ), + const SizedBox(width: 12), + // 标题 + const Text( + 'The Kinetic Vault', + style: TextStyle( + color: AppColorScheme.darkPrimary, + fontWeight: FontWeight.w900, + letterSpacing: -0.5, + fontSize: 16, + ), + ), + ], + ); + }, + ), + // 右侧操作按钮 + Row( + children: [ + _IconBtn( + icon: LucideIcons.search, + onTap: () {}, + ), + const SizedBox(width: 8), + _IconBtn( + icon: LucideIcons.bell, + onTap: () {}, + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} + +/// 图标按钮 +class _IconBtn extends StatelessWidget { + final IconData icon; + final VoidCallback onTap; + + const _IconBtn({required this.icon, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(8), + child: Icon( + icon, + color: AppColorScheme.darkOnSurfaceVariant, + size: 22, + ), + ), + ); + } +} + +/// 底部导航栏 - "The Kinetic Vault" 设计风格 class _BottomNavBar extends StatelessWidget { final List<_NavItem> items; final int currentIndex; @@ -75,25 +201,41 @@ class _BottomNavBar extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - return Container( decoration: BoxDecoration( - color: theme.colorScheme.background, - border: Border(top: BorderSide(color: theme.colorScheme.border)), + color: AppColorScheme.darkSurfaceLow.withValues(alpha: 0.8), + borderRadius: const BorderRadius.vertical(top: Radius.circular(32)), + border: Border( + top: BorderSide( + color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15), + ), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.5), + blurRadius: 40, + offset: const Offset(0, -10), + ), + ], ), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: items.asMap().entries.map((entry) { - return _NavItemWidget( - item: entry.value, - isSelected: entry.key == currentIndex, - onTap: () => onTap(entry.key), - ); - }).toList(), + child: ClipRRect( + borderRadius: const BorderRadius.vertical(top: Radius.circular(32)), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: items.asMap().entries.map((entry) { + return _NavItemWidget( + item: entry.value, + isSelected: entry.key == currentIndex, + onTap: () => onTap(entry.key), + ); + }).toList(), + ), + ), ), ), ), @@ -115,28 +257,31 @@ class _NavItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); - final color = isSelected ? theme.colorScheme.primary : theme.colorScheme.mutedForeground; + final color = isSelected ? AppColorScheme.darkPrimary : AppColorScheme.darkOnSurfaceVariant; return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(item.icon, color: color, size: 24), - const SizedBox(height: 4), - Text( - item.label, - style: TextStyle( - fontSize: 12, - color: color, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - ), - ), - ], + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: const EdgeInsets.all(12), + decoration: isSelected + ? BoxDecoration( + color: AppColorScheme.darkPrimary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColorScheme.darkPrimary.withValues(alpha: 0.3), + blurRadius: 15, + spreadRadius: 0, + ), + ], + ) + : null, + child: Icon( + item.icon, + color: color, + size: 24, ), ), );