From d8cd38c4de9ce7169c8c42e37d63444a9574004a Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Sun, 5 Apr 2026 22:24:04 +0800 Subject: [PATCH] feat(theme): update color scheme with new Slate theme and improved surface hierarchy Updated the app's color scheme to implement a new "Slate" theme with refined dark and light variants. Changed background colors from #0A0E14 to #0B1120 for dark mode and updated surface layer colors to follow Material Design 3 specifications. Modified text colors and outline variants for better contrast and accessibility. Updated font sizes in transaction details screen from 11px to 12px for improved readability. --- .../lib/core/constants/app_colors.dart | 155 --- .../lib/core/theme/app_color_scheme.dart | 174 ++- .../lib/core/theme/app_colors.dart | 145 --- .../lib/core/theme/app_text_styles.dart | 28 +- .../lib/ui/pages/asset/asset_page.dart | 988 ++++++++++-------- .../lib/ui/pages/asset/transfer_page.dart | 773 +++++++------- .../lib/ui/pages/auth/login_page.dart | 338 ++++-- .../lib/ui/pages/home/header_bar.dart | 108 ++ .../lib/ui/pages/home/home_page.dart | 54 +- .../lib/ui/pages/home/hot_coins_section.dart | 199 ++++ .../lib/ui/pages/home/quick_actions_row.dart | 124 +++ .../lib/ui/pages/market/market_page.dart | 448 +++++--- .../lib/ui/pages/mine/mine_page.dart | 913 +++++++++------- .../ui/pages/mine/welfare_center_page.dart | 918 +++++++++------- .../lib/ui/pages/orders/fund_orders_page.dart | 714 ++++++++----- .../lib/ui/pages/trade/trade_page.dart | 821 +++++++-------- .../lib/ui/shared/ui_constants.dart | 2 +- 17 files changed, 3980 insertions(+), 2922 deletions(-) delete mode 100644 flutter_monisuo/lib/core/constants/app_colors.dart delete mode 100644 flutter_monisuo/lib/core/theme/app_colors.dart create mode 100644 flutter_monisuo/lib/ui/pages/home/header_bar.dart create mode 100644 flutter_monisuo/lib/ui/pages/home/hot_coins_section.dart create mode 100644 flutter_monisuo/lib/ui/pages/home/quick_actions_row.dart diff --git a/flutter_monisuo/lib/core/constants/app_colors.dart b/flutter_monisuo/lib/core/constants/app_colors.dart deleted file mode 100644 index f4cb106..0000000 --- a/flutter_monisuo/lib/core/constants/app_colors.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:flutter/material.dart'; - -/// 应用颜色常量 - 统一的颜色系统 -/// -/// 设计原则: -/// 1. 语义化命名 - 颜色按用途命名,而非外观 -/// 2. 对比度保证 - 文字与背景对比度 >= 4.5:1 (WCAG AA) -/// 3. 一致性 - 同一语义用途使用同一颜色 -class AppColors { - AppColors._(); - - // ============================================ - // 品牌色 (Brand Colors) - 专业蓝 - // ============================================ - - /// 主品牌色 - 专业蓝,代表信任与稳定 - static const Color primary = Color(0xFF2563EB); - - /// 主品牌色浅色变体 - static const Color primaryLight = Color(0xFF3B82F6); - - /// 主品牌色深色变体 - static const Color primaryDark = Color(0xFF1D4ED8); - - /// 主品牌色渐变 - static const LinearGradient primaryGradient = LinearGradient( - colors: [primary, primaryDark], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - - // ============================================ - // 语义色 (Semantic Colors) - // ============================================ - - /// 成功/上涨 - 绿色系 - static const Color success = Color(0xFF00C853); - static const Color up = success; - - /// 警告 - 橙色系 - static const Color warning = Color(0xFFFF9800); - - /// 错误/下跌 - 红色系 - static const Color error = Color(0xFFFF5252); - static const Color down = error; - - /// 信息 - 蓝色系 - static const Color info = Color(0xFF2196F3); - - /// 交易类型色 - static const Color deposit = success; - static const Color withdraw = warning; - static const Color trade = info; - - // ============================================ - // 深色主题背景色 (Dark Theme Backgrounds) - // ============================================ - - /// 主背景色 - 最深的背景 - static const Color background = Color(0xFF0F0F1A); - - /// 卡片背景色 - static const Color cardBackground = Color(0xFF1A1A2E); - - /// Scaffold 背景色 - static const Color scaffoldBackground = background; - - /// 表面色 - 用于弹出层、对话框 - static const Color surface = Color(0xFF16213E); - - /// 悬停状态背景 - static const Color hoverBackground = Color(0xFF252542); - - // ============================================ - // 文字颜色 (Text Colors) - // 对比度均 >= 4.5:1 (基于深色背景) - // ============================================ - - /// 主要文字 - 白色,对比度 21:1 - static const Color textPrimary = Color(0xFFFFFFFF); - - /// 次要文字 - 浅灰色,对比度 ~10:1 - static const Color textSecondary = Color(0xFFB0B0B0); - - /// 提示文字 - 中灰色,对比度 ~5:1 - static const Color textHint = Color(0xFF808080); - - /// 禁用文字 - 深灰色,对比度 ~3:1 - static const Color textDisabled = Color(0xFF4D4D4D); - - /// 链接文字 - static const Color textLink = primary; - - // ============================================ - // 边框和分割线 (Borders & Dividers) - // ============================================ - - /// 默认边框色 - static const Color border = Color(0xFF2A2A45); - - /// 分割线颜色 - static const Color divider = border; - - /// 焦点边框色 - static const Color focusBorder = primary; - - /// 输入框边框色 - static const Color inputBorder = Color(0xFF3A3A55); - - // ============================================ - // 输入框颜色 (Input Colors) - // ============================================ - - /// 输入框背景 - static const Color inputBackground = cardBackground; - - /// 输入框焦点边框 - static const Color inputFocusBorder = primary; - - // ============================================ - // 按钮渐变 (Button Gradients) - // ============================================ - - /// 买入按钮渐变 - static const LinearGradient buyGradient = LinearGradient( - colors: [Color(0xFF00C853), Color(0xFF00A844)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - - /// 卖出按钮渐变 - static const LinearGradient sellGradient = LinearGradient( - colors: [Color(0xFFFF5252), Color(0xFFD32F2F)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - - // ============================================ - // 渐变色 (Gradient Colors) - // ============================================ - - /// 资产卡片渐变色 - static const List gradientColors = [primary, primaryDark]; - - // ============================================ - // 工具方法 (Utility Methods) - // ============================================ - - /// 获取涨跌颜色 - static Color getChangeColor(bool isUp) => isUp ? up : down; - - /// 获取涨跌背景色(带透明度) - static Color getChangeBackgroundColor(bool isUp) => - isUp ? up.withValues(alpha: 0.15) : down.withValues(alpha: 0.15); -} diff --git a/flutter_monisuo/lib/core/theme/app_color_scheme.dart b/flutter_monisuo/lib/core/theme/app_color_scheme.dart index 46cf555..dbe8231 100644 --- a/flutter_monisuo/lib/core/theme/app_color_scheme.dart +++ b/flutter_monisuo/lib/core/theme/app_color_scheme.dart @@ -20,23 +20,23 @@ class AppColorScheme { AppColorScheme._(); // ============================================ - // 深色主题 - "黑金传奇" (Material Design 3) - // 背景 #0A0E14 | 主色 #1E3A8A | 强调 #D4AF37 + // 深色主题 - "Slate Dark" (Material Design 3) + // 背景 #0B1120 | 主色 #1E3A8A | 强调 #D4AF37 // ============================================ - /// 背景基色 - 深邃黑 - static const Color darkBackground = Color(0xFF0A0E14); + /// 背景基色 - Slate 深蓝黑 + static const Color darkBackground = Color(0xFF0B1120); /// Surface 层次 (从低到高) - Material Design 3 规范 - static const Color darkSurfaceDim = Color(0xFF0A0E14); + static const Color darkSurfaceDim = Color(0xFF0B1120); static const Color darkSurfaceLowest = Color(0xFF000000); - static const Color darkSurfaceLow = Color(0xFF0F1219); - static const Color darkSurface = Color(0xFF0A0E14); - static const Color darkSurfaceContainer = Color(0xFF151921); - static const Color darkSurfaceContainerHigh = Color(0xFF1B1F28); - static const Color darkSurfaceContainerHighest = Color(0xFF21252F); - static const Color darkSurfaceBright = Color(0xFF272B35); - static const Color darkSurfaceVariant = Color(0xFF21252F); + static const Color darkSurfaceLow = Color(0xFF0F172A); + static const Color darkSurface = Color(0xFF0B1120); + static const Color darkSurfaceContainer = Color(0xFF0F172A); + static const Color darkSurfaceContainerHigh = Color(0xFF1E293B); + static const Color darkSurfaceContainerHighest = Color(0xFF253349); + static const Color darkSurfaceBright = Color(0xFF334155); + static const Color darkSurfaceVariant = Color(0xFF1E293B); /// 兼容旧名称 static const Color darkSurfaceContainerLowest = darkSurfaceLowest; @@ -45,8 +45,8 @@ class AppColorScheme { static const Color darkSurfaceHighest = darkSurfaceContainerHighest; /// Ghost Border - static const Color darkOutline = Color(0xFF73757d); - static const Color darkOutlineVariant = Color(0xFF45484f); + static const Color darkOutline = Color(0xFF64748B); + static const Color darkOutlineVariant = Color(0xFF334155); /// Primary - 专业蓝 #1E3A8A (主要交互) static const Color darkPrimary = Color(0xFF1E3A8A); @@ -89,38 +89,38 @@ class AppColorScheme { static const Color darkOnErrorContainer = Color(0xFFffa8a3); /// 文本色 - static const Color darkOnSurface = Color(0xFFecedf6); - static const Color darkOnSurfaceVariant = Color(0xFFa9abb3); - static const Color darkOnSurfaceMuted = Color(0xFF6b6d75); - static const Color darkOnBackground = Color(0xFFecedf6); - static const Color darkInverseSurface = Color(0xFFf9f9ff); - static const Color darkInverseOnSurface = Color(0xFF52555c); + static const Color darkOnSurface = Color(0xFFF8FAFC); + static const Color darkOnSurfaceVariant = Color(0xFF94A3B8); + static const Color darkOnSurfaceMuted = Color(0xFF64748B); + static const Color darkOnBackground = Color(0xFFF8FAFC); + static const Color darkInverseSurface = Color(0xFFF8FAFC); + static const Color darkInverseOnSurface = Color(0xFF475569); static const Color darkInversePrimary = Color(0xFF1E40AF); static const Color darkSurfaceTint = Color(0xFFD4AF37); // ============================================ - // 浅色主题 - "白金殿堂" - // 背景 #FAFAFA | 主色 #1E40AF | 强调 #FFD700 + // 浅色主题 - "Slate Light" (Material Design 3) + // 背景 #F8FAFC | 主色 #1E40AF | 强调 #D4AF37 // ============================================ - /// 背景基色 - 纯净白 - static const Color lightBackground = Color(0xFFFAFAFA); + /// 背景基色 - Slate 50 + static const Color lightBackground = Color(0xFFF8FAFC); /// Surface 层次 (从低到高) - static const Color lightSurfaceLowest = Color(0xFFffffff); - static const Color lightSurfaceLow = Color(0xFFF5F5F5); - static const Color lightSurface = Color(0xFFFAFAFA); - static const Color lightSurfaceHigh = Color(0xFFF0F0F0); - static const Color lightSurfaceHighest = Color(0xFFE8E8E8); + static const Color lightSurfaceLowest = Color(0xFFFFFFFF); + static const Color lightSurfaceLow = Color(0xFFF1F5F9); + static const Color lightSurface = Color(0xFFF8FAFC); + static const Color lightSurfaceHigh = Color(0xFFE2E8F0); + static const Color lightSurfaceHighest = Color(0xFFCBD5E1); - /// Ghost Border - static const Color lightOutlineVariant = Color(0xFFD0D0D0); + /// Ghost Border - Slate 300 + static const Color lightOutlineVariant = Color(0xFFCBD5E1); /// Primary - 专业蓝 #1E40AF (主要交互) static const Color lightPrimary = Color(0xFF1E40AF); static const Color lightPrimaryContainer = Color(0xFF3B82F6); - /// Secondary - 亮金 #FFD700 (白金强调色) + /// Secondary - 亮金 #D4AF37 (白金强调色) static const Color lightSecondary = Color(0xFFD4AF37); static const Color lightSecondaryContainer = Color(0xFFFFE44D); @@ -128,10 +128,10 @@ class AppColorScheme { static const Color lightTertiary = Color(0xFF00875A); static const Color lightTertiaryContainer = Color(0xFFd4f5e9); - /// 文本色 - static const Color lightOnSurface = Color(0xFF1A1A1A); - static const Color lightOnSurfaceVariant = Color(0xFF5a5d60); - static const Color lightOnSurfaceMuted = Color(0xFF8a8d90); + /// 文本色 - Slate + static const Color lightOnSurface = Color(0xFF0F172A); + static const Color lightOnSurfaceVariant = Color(0xFF475569); + static const Color lightOnSurfaceMuted = Color(0xFF94A3B8); // ============================================ // Glass Panel 毛玻璃效果颜色 @@ -369,19 +369,19 @@ class AppColorScheme { // ============================================ /// 浅色主题 Error 色 - static const Color lightError = Color(0xFFd7383b); + static const Color lightError = Color(0xFFDC2626); static const Color lightOnError = Color(0xFFFFFFFF); - /// 浅色主题 Outline 色 - static const Color lightOutline = Color(0xFF73757d); + /// 浅色主题 Outline 色 - Slate 500 + static const Color lightOutline = Color(0xFF64748B); - /// 浅色主题 Surface 色 (扩展) - static const Color lightSurfaceBright = Color(0xFFffffff); - static const Color lightSurfaceDim = Color(0xFFF0F0F0); - static const Color lightSurfaceVariant = Color(0xFFF0F0F0); - static const Color lightSurfaceContainer = Color(0xFFF5F5F5); - static const Color lightSurfaceContainerHigh = Color(0xFFF0F0F0); - static const Color lightSurfaceContainerHighest = Color(0xFFE8E8E8); + /// 浅色主题 Surface 色 (扩展) - Slate 系列 + static const Color lightSurfaceBright = Color(0xFFFFFFFF); + static const Color lightSurfaceDim = Color(0xFFE2E8F0); + static const Color lightSurfaceVariant = Color(0xFFE2E8F0); + static const Color lightSurfaceContainer = Color(0xFFF1F5F9); + static const Color lightSurfaceContainerHigh = Color(0xFFE2E8F0); + static const Color lightSurfaceContainerHighest = Color(0xFFCBD5E1); static ColorScheme get lightMaterial => ColorScheme.light( primary: lightPrimary, @@ -419,62 +419,44 @@ class AppColorScheme { ); // ============================================ - // 兼容性常量 (已废弃,保留向后兼容) + // 兼容性别名(替代 theme/app_colors.dart) + // 映射旧名到新系统,避免 breaking change // ============================================ - @Deprecated('Use darkPrimary instead') - static const Color primaryDark = darkPrimary; + // 背景色 + static const Color background = darkBackground; + static const Color cardBackground = darkSurfaceContainer; + static const Color inputBackground = darkSurfaceContainerHigh; + static const Color scaffoldBackground = darkBackground; + static const Color modalBackground = darkSurfaceContainerHigh; + static const Color hoverBackground = darkSurfaceBright; - @Deprecated('Use lightPrimary instead') - static const Color primaryLight = lightPrimary; + // 文字色 + static const Color textPrimary = darkOnSurface; + static const Color textSecondary = darkOnSurfaceVariant; + static const Color textHint = darkOnSurfaceMuted; + static const Color textDisabled = darkInverseSurface; + static const Color textLink = darkPrimary; - @Deprecated('Use darkBackground instead') - static const Color _darkBackground = darkBackground; + // 边框色 + static const Color border = darkOutlineVariant; + static const Color divider = darkSurfaceContainer; + static const Color inputBorder = darkOnSurfaceMuted; + static const Color inputFocusBorder = darkPrimary; + static const Color focusBorder = darkPrimary; - @Deprecated('Use darkSurfaceContainer instead') - static const Color _darkCardBackground = darkSurfaceContainer; + // 交易类型色 + static const Color deposit = up; + static const Color withdraw = warning; + static const Color trade = info; - @Deprecated('Use darkSurfaceContainerHigh instead') - static const Color _darkSecondary = darkSurfaceContainerHigh; - - @Deprecated('Use darkSurfaceContainerHigh instead') - static const Color _darkMuted = darkSurfaceContainerHigh; - - @Deprecated('Use darkOutlineVariant instead') - static const Color _darkBorder = darkOutlineVariant; - - @Deprecated('Use darkOnSurface instead') - static const Color _darkTextPrimary = darkOnSurface; - - @Deprecated('Use darkOnSurfaceVariant instead') - static const Color _darkTextSecondary = darkOnSurfaceVariant; - - @Deprecated('Use darkOnSurfaceMuted instead') - static const Color _darkTextHint = darkOnSurfaceMuted; - - @Deprecated('Use lightBackground instead') - static const Color _lightBackground = lightBackground; - - @Deprecated('Use lightSurfaceLowest instead') - static const Color _lightCardBackground = lightSurfaceLowest; - - @Deprecated('Use lightSurfaceHigh instead') - static const Color _lightSecondary = lightSurfaceHigh; - - @Deprecated('Use lightSurfaceHigh instead') - static const Color _lightMuted = lightSurfaceHigh; - - @Deprecated('Use lightOutlineVariant instead') - static const Color _lightBorder = lightOutlineVariant; - - @Deprecated('Use lightOnSurface instead') - static const Color _lightTextPrimary = lightOnSurface; - - @Deprecated('Use lightOnSurfaceVariant instead') - static const Color _lightTextSecondary = lightOnSurfaceVariant; - - @Deprecated('Use lightOnSurfaceMuted instead') - static const Color _lightTextHint = lightOnSurfaceMuted; + // 旧渐变 + static const List gradientColors = [darkPrimary, darkPrimaryContainer]; + static const LinearGradient primaryGradient = LinearGradient( + colors: [darkPrimary, darkPrimaryContainer], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ); } /// 创建 Shadcn 深色主题 diff --git a/flutter_monisuo/lib/core/theme/app_colors.dart b/flutter_monisuo/lib/core/theme/app_colors.dart deleted file mode 100644 index cd8fe68..0000000 --- a/flutter_monisuo/lib/core/theme/app_colors.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:flutter/material.dart'; - -/// 数字货币应用颜色系统 -/// -/// 设计原则: -/// 1. 所有文字与背景对比度 >= 4.5:1 (WCAG AA) -/// 2. 涨跌色使用国际通用标准 (绿涨红跌) -/// 3. 背景色层次分明,易于区分 -/// 4. 杜绝文字和背景颜色一样无法区分的情况 -class AppColors { - AppColors._(); - - // ============================================ - // 品牌色 (Brand Colors) - 专业蓝 - // ============================================ - - /// 主色 - 专业蓝,代表信任与稳定 - static const Color primary = Color(0xFF2563EB); - static const Color primaryLight = Color(0xFF3B82F6); - static const Color primaryDark = Color(0xFF1D4ED8); - - /// 主色渐变 - 用于卡片、按钮等 - static const LinearGradient primaryGradient = LinearGradient( - colors: [primary, primaryDark], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - - // ============================================ - // 交易色 (Trading Colors) - // ============================================ - - /// 涨/买入 - 标准绿色 (国际通用) - static const Color up = Color(0xFF00C853); - - /// 跌/卖出 - 标准红色 (国际通用) - static const Color down = Color(0xFFFF5252); - - /// 买入按钮渐变 - static const LinearGradient buyGradient = LinearGradient( - colors: [Color(0xFF00C853), Color(0xFF00A844)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - - /// 卖出按钮渐变 - static const LinearGradient sellGradient = LinearGradient( - colors: [Color(0xFFFF5252), Color(0xFFD32F2F)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - - // ============================================ - // 功能色 (Semantic Colors) - // ============================================ - - /// 成功 - static const Color success = Color(0xFF00C853); - - /// 警告 - static const Color warning = Color(0xFFFF9800); - - /// 错误 - static const Color error = Color(0xFFFF5252); - - /// 信息 - static const Color info = Color(0xFF2196F3); - - /// 充值 - static const Color deposit = Color(0xFF00C853); - - /// 提现 - static const Color withdraw = Color(0xFFFF9800); - - /// 划转/交易 - static const Color trade = Color(0xFF2196F3); - - // ============================================ - // 背景色 (Dark Theme Backgrounds) - // ============================================ - - /// 页面背景 - 最深 - static const Color background = Color(0xFF0F0F1A); - - /// 卡片背景 - 中等深度 - static const Color cardBackground = Color(0xFF1A1A2E); - - /// 输入框背景 - 稍浅 - static const Color inputBackground = Color(0xFF16213E); - - /// Scaffold 背景 (兼容旧代码) - static const Color scaffoldBackground = Color(0xFF0F0F1A); - - /// 模态框背景 - static const Color modalBackground = Color(0xFF1E1E32); - - // ============================================ - // 文字颜色 (Text Colors) - // ============================================ - - /// 主要文字 - 白色,对比度 21:1 - static const Color textPrimary = Color(0xFFFFFFFF); - - /// 次要文字 - 浅灰蓝,对比度约 8:1 - static const Color textSecondary = Color(0xFFB0B0C0); - - /// 提示文字 - 中灰,对比度约 4.7:1 - static const Color textHint = Color(0xFF6B6B80); - - /// 禁用文字 - 暗灰 - static const Color textDisabled = Color(0xFF4A4A5A); - - /// 链接文字 - 品牌蓝 - static const Color textLink = Color(0xFF2563EB); - - // ============================================ - // 边框与分割线 (Borders & Dividers) - // ============================================ - - /// 边框 - 低透明度白色 - static const Color border = Color(0x14FFFFFF); // 8% white - - /// 分割线 - 更低透明度 - static const Color divider = Color(0x0FFFFFFF); // 6% white - - /// 输入框边框 - static const Color inputBorder = Color(0x1AFFFFFF); // 10% white - - /// 输入框聚焦边框 - 品牌蓝 - static const Color inputFocusBorder = Color(0xFF2563EB); - - // ============================================ - // 便捷方法 - // ============================================ - - /// 根据涨跌获取颜色 - static Color getChangeColor(bool isUp) => isUp ? up : down; - - /// 获取带透明度的涨跌背景色 - static Color getChangeBackgroundColor(bool isUp) => - isUp ? up.withValues(alpha: 0.15) : down.withValues(alpha: 0.15); - - /// 渐变色 (兼容旧代码) - 品牌蓝 - static const List gradientColors = [Color(0xFF2563EB), Color(0xFF1D4ED8)]; -} diff --git a/flutter_monisuo/lib/core/theme/app_text_styles.dart b/flutter_monisuo/lib/core/theme/app_text_styles.dart index 3fe2f1a..bd0a4e4 100644 --- a/flutter_monisuo/lib/core/theme/app_text_styles.dart +++ b/flutter_monisuo/lib/core/theme/app_text_styles.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'app_colors.dart'; +import 'app_color_scheme.dart'; /// 文字样式系统 /// @@ -16,7 +16,7 @@ class AppTextStyles { static const TextStyle h1 = TextStyle( fontSize: 22, fontWeight: FontWeight.bold, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.3, ); @@ -24,7 +24,7 @@ class AppTextStyles { static const TextStyle h2 = TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.3, ); @@ -32,7 +32,7 @@ class AppTextStyles { static const TextStyle h3 = TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.4, ); @@ -40,7 +40,7 @@ class AppTextStyles { static const TextStyle h4 = TextStyle( fontSize: 15, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.4, ); @@ -52,7 +52,7 @@ class AppTextStyles { static const TextStyle body1 = TextStyle( fontSize: 14, fontWeight: FontWeight.normal, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.5, ); @@ -60,7 +60,7 @@ class AppTextStyles { static const TextStyle body2 = TextStyle( fontSize: 13, fontWeight: FontWeight.normal, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.5, ); @@ -72,7 +72,7 @@ class AppTextStyles { static const TextStyle caption = TextStyle( fontSize: 12, fontWeight: FontWeight.normal, - color: AppColors.textSecondary, + color: AppColorScheme.textSecondary, height: 1.4, ); @@ -80,7 +80,7 @@ class AppTextStyles { static const TextStyle small = TextStyle( fontSize: 11, fontWeight: FontWeight.normal, - color: AppColors.textSecondary, + color: AppColorScheme.textSecondary, height: 1.3, ); @@ -88,7 +88,7 @@ class AppTextStyles { static const TextStyle hint = TextStyle( fontSize: 13, fontWeight: FontWeight.normal, - color: AppColors.textHint, + color: AppColorScheme.textHint, height: 1.4, ); @@ -100,7 +100,7 @@ class AppTextStyles { static const TextStyle amount = TextStyle( fontSize: 22, fontWeight: FontWeight.bold, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.2, fontFeatures: [FontFeature.tabularFigures()], ); @@ -109,7 +109,7 @@ class AppTextStyles { static const TextStyle price = TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.3, fontFeatures: [FontFeature.tabularFigures()], ); @@ -126,7 +126,7 @@ class AppTextStyles { static const TextStyle button = TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: AppColors.textPrimary, + color: AppColorScheme.textPrimary, height: 1.2, ); @@ -134,7 +134,7 @@ class AppTextStyles { static const TextStyle link = TextStyle( fontSize: 13, fontWeight: FontWeight.w500, - color: AppColors.textLink, + color: AppColorScheme.textLink, decoration: TextDecoration.underline, height: 1.4, ); diff --git a/flutter_monisuo/lib/ui/pages/asset/asset_page.dart b/flutter_monisuo/lib/ui/pages/asset/asset_page.dart index 4d52716..fce4a0b 100644 --- a/flutter_monisuo/lib/ui/pages/asset/asset_page.dart +++ b/flutter_monisuo/lib/ui/pages/asset/asset_page.dart @@ -5,10 +5,12 @@ import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:provider/provider.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../core/utils/toast_utils.dart'; import '../../../core/event/app_event_bus.dart'; +import '../../../data/models/account_models.dart'; import '../../../providers/asset_provider.dart'; import '../../../providers/auth_provider.dart'; import '../../shared/ui_constants.dart'; @@ -17,7 +19,7 @@ import '../../components/neon_glow.dart'; import '../orders/fund_orders_page.dart'; import 'transfer_page.dart'; -/// 资产页面 - Material Design 3 风格 +/// 资产页面 - Matching .pen design spec (CMcqs) class AssetPage extends StatefulWidget { const AssetPage({super.key}); @@ -75,21 +77,52 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi backgroundColor: colorScheme.surfaceContainerHighest, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - padding: AppSpacing.pagePadding, + padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md + 8, AppSpacing.md, 32), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - _TabSelector( - tabs: const ['资金账户', '交易账户'], + // Page title: "资产" 22px bold — matching .pen titleFrame padding [16,0,8,0] + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 8), + child: Text( + '资产', + style: GoogleFonts.inter( + fontSize: 22, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, + ), + ), + ), + const SizedBox(height: AppSpacing.sm), + // Account tab switcher — pill-style matching .pen UE6xC + _AccountTabSwitcher( selectedIndex: _activeTab, onChanged: (index) => setState(() => _activeTab = index), ), - SizedBox(height: AppSpacing.md), - _activeTab == 0 - ? _FundAccountCard(provider: provider) - : _TradeAccountCard( - holdings: provider.holdings, - tradeBalance: provider.overview?.tradeBalance, - ), + const SizedBox(height: AppSpacing.md), + // Balance card — matching .pen 59637 (cornerRadius lg, stroke, padding 20, gap 12) + _BalanceCard( + provider: provider, + activeTab: _activeTab, + ), + const SizedBox(height: AppSpacing.md), + // Action buttons row — matching .pen pIpHe (gap 12) + _ActionButtonsRow( + onDeposit: () => _showDepositDialog(context), + onWithdraw: () => _showWithdrawDialog(context, provider.fundAccount?.balance), + onTransfer: () => _navigateToTransfer(context), + ), + const SizedBox(height: AppSpacing.md), + // Records link row — matching .pen fLHtq (cornerRadius lg, padding [14,16], stroke) + _RecordsLinkRow( + onTap: () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const FundOrdersPage()), + ), + ), + const SizedBox(height: AppSpacing.md), + // Holdings section — matching .pen th9BG + 6X6tC + _HoldingsSection(holdings: _activeTab == 1 ? provider.holdings : []), ], ), ), @@ -100,93 +133,19 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi } } -/// 资产总览卡片 - Material Design 3 风格 -class _AssetCard extends StatelessWidget { - final dynamic overview; +// ============================================ +// Account Tab Switcher — .pen node UE6xC +// height: 40, padding: 3, cornerRadius: md, fill: $bg-tertiary +// activeTab: fill $bg-primary, cornerRadius sm, shadow blur 3, color #0000000D, offset y 1 +// activeTabText: 14px, fontWeight 600, fill $text-primary +// inactiveTabText: 14px, fontWeight 500, fill $text-secondary +// ============================================ - const _AssetCard({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.lg + AppSpacing.sm), - decoration: BoxDecoration( - gradient: AppColorScheme.assetCardGradient, - borderRadius: BorderRadius.circular(AppRadius.xl), - boxShadow: [ - BoxShadow( - color: colorScheme.primary.withOpacity(isDark ? 0.15 : 0.08), - blurRadius: 20, - ), - ], - ), - child: Column( - children: [ - Text( - 'PORTFOLIO VALUE', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w700, - letterSpacing: 0.2, - color: Colors.white.withOpacity(0.7), - ), - ), - SizedBox(height: AppSpacing.sm), - Text( - '\$${overview?.totalAsset ?? '0.00'}', - style: GoogleFonts.spaceGrotesk( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - SizedBox(height: AppSpacing.md), - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, - vertical: AppSpacing.xs + AppSpacing.xs, - ), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.full), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - LucideIcons.trendingUp, - color: Colors.white.withOpacity(0.7), - size: 14, - ), - SizedBox(width: AppSpacing.xs), - Text( - '总盈亏: ${overview?.totalProfit ?? '0.00'} USDT', - style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.7), - ), - ), - ], - ), - ), - ], - ), - ); - } -} - -/// Tab 选择器 - Material Design 3 风格 -class _TabSelector extends StatelessWidget { - final List tabs; +class _AccountTabSwitcher extends StatelessWidget { final int selectedIndex; final ValueChanged onChanged; - const _TabSelector({ - required this.tabs, + const _AccountTabSwitcher({ required this.selectedIndex, required this.onChanged, }); @@ -197,301 +156,260 @@ class _TabSelector extends StatelessWidget { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( - padding: EdgeInsets.all(AppSpacing.xs), + height: 40, + padding: const EdgeInsets.all(3), decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(AppRadius.lg), + color: isDark ? colorScheme.surfaceContainerHighest : colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.md), ), child: Row( - children: tabs.asMap().entries.map((entry) { - final index = entry.key; - final label = entry.value; - final isSelected = index == selectedIndex; - - return Expanded( - child: GestureDetector( - onTap: () => onChanged(index), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs), - decoration: BoxDecoration( - color: isSelected ? colorScheme.primary : Colors.transparent, - borderRadius: BorderRadius.circular(AppRadius.md), - boxShadow: isSelected - ? [ - BoxShadow( - color: colorScheme.primary.withOpacity(isDark ? 0.15 : 0.08), - blurRadius: 10, - ), - ] - : null, - ), - child: Center( - child: Text( - label, - style: TextStyle( - color: isSelected ? colorScheme.onPrimary : colorScheme.onSurfaceVariant, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - ), - ), - ), - ), - ), - ); - }).toList(), - ), - ); - } -} - -/// 资金账户卡片 - Glass Panel 风格 -class _FundAccountCard extends StatelessWidget { - final AssetProvider provider; - - const _FundAccountCard({required this.provider}); - - @override - Widget build(BuildContext context) { - final fund = provider.fundAccount; - final overview = provider.overview; - final colorScheme = Theme.of(context).colorScheme; - - // 优先使用fund数据,如果为null则使用overview的fundBalance - final displayBalance = fund?.balance ?? overview?.fundBalance ?? '0.00'; - - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.xs), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'USDT 余额', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const FundOrdersPage()), - ), - child: Row( - children: [ - Text( - '充提记录', - style: TextStyle( - color: colorScheme.primary, - fontSize: 12, - ), - ), - Icon( - LucideIcons.chevronRight, - size: 14, - color: colorScheme.primary, - ), - ], - ), - ), - ], + _buildTab( + context: context, + label: '资金账户', + isSelected: selectedIndex == 0, + onTap: () => onChanged(0), + isDark: isDark, ), - SizedBox(height: AppSpacing.sm), - Text( - displayBalance, - style: GoogleFonts.spaceGrotesk( - fontSize: 20, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - SizedBox(height: AppSpacing.lg), - Row( - children: [ - Expanded( - child: NeonButton( - text: '充值', - type: NeonButtonType.tertiary, - onPressed: () => _showDepositDialog(context), - height: 44, - showGlow: false, - ), - ), - SizedBox(width: AppSpacing.sm), - Expanded( - child: NeonButton( - text: '提现', - type: NeonButtonType.secondary, - onPressed: () => _showWithdrawDialog(context, fund?.balance), - height: 44, - showGlow: false, - ), - ), - SizedBox(width: AppSpacing.sm), - Expanded( - child: NeonButton( - text: '划转', - type: NeonButtonType.outline, - onPressed: () => _navigateToTransfer(context), - height: 44, - showGlow: false, - ), - ), - ], + _buildTab( + context: context, + label: '交易账户', + isSelected: selectedIndex == 1, + onTap: () => onChanged(1), + isDark: isDark, ), ], ), ); } + + Widget _buildTab({ + required BuildContext context, + required String label, + required bool isSelected, + required VoidCallback onTap, + required bool isDark, + }) { + final colorScheme = Theme.of(context).colorScheme; + + return Expanded( + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: isSelected + ? colorScheme.surface + : Colors.transparent, + borderRadius: BorderRadius.circular(AppRadius.sm), + boxShadow: isSelected + ? [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 3, + offset: const Offset(0, 1), + ), + ] + : null, + ), + alignment: Alignment.center, + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + color: isSelected ? colorScheme.onSurface : colorScheme.onSurfaceVariant, + ), + ), + ), + ), + ); + } } -/// 交易账户卡片 - Glass Panel 风格 -class _TradeAccountCard extends StatelessWidget { - final List holdings; - final String? tradeBalance; +// ============================================ +// Balance Card — .pen node 59637 +// cornerRadius: lg, fill: $surface-card, padding: 20, stroke: $border-default 1px, gap: 12 +// balLabel: "USDT 余额" 12px normal $text-secondary +// balAmount: "25,680.50" 28px w700 $text-primary +// balSubRow: "≈ $25,680.50 USD" 12px normal $text-muted +// ============================================ - const _TradeAccountCard({required this.holdings, this.tradeBalance}); +class _BalanceCard extends StatelessWidget { + final AssetProvider provider; + final int activeTab; + + const _BalanceCard({ + required this.provider, + required this.activeTab, + }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; - // 计算总市值(所有持仓折算成USDT) - double totalValue = 0; - for (var h in holdings) { - final value = double.tryParse(h.currentValue?.toString() ?? '0') ?? 0; - totalValue += value; - } - - // 对持仓进行排序:USDT 放在最上面 - final sortedHoldings = List.from(holdings); - sortedHoldings.sort((a, b) { - final codeA = (a.coinCode ?? a['coinCode'] ?? '').toString().toUpperCase(); - final codeB = (b.coinCode ?? b['coinCode'] ?? '').toString().toUpperCase(); - if (codeA == 'USDT') return -1; - if (codeB == 'USDT') return 1; - return 0; - }); + final displayBalance = activeTab == 0 + ? (provider.fundAccount?.balance ?? provider.overview?.fundBalance ?? '0.00') + : _calculateTradeTotal(); return GlassPanel( - padding: AppSpacing.cardPadding, + padding: const EdgeInsets.all(20), + borderRadius: BorderRadius.circular(AppRadius.lg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 标题行 - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.md), - ), - child: Icon( - LucideIcons.trendingUp, - size: 18, - color: colorScheme.primary, - ), - ), - SizedBox(width: AppSpacing.sm), - Text( - '交易账户', - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - ], - ), - Icon( - LucideIcons.chevronRight, - size: 14, - color: colorScheme.primary, - ), - ], - ), - SizedBox(height: AppSpacing.md), - // 余额 Text( - '余额 (USDT)', - style: TextStyle( - fontSize: 11, + 'USDT 余额', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, color: colorScheme.onSurfaceVariant, ), ), - SizedBox(height: AppSpacing.xs), + const SizedBox(height: 12), Text( - totalValue.toStringAsFixed(2), - style: GoogleFonts.spaceGrotesk( - fontSize: 20, - fontWeight: FontWeight.bold, + _formatBalance(displayBalance), + style: GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), - SizedBox(height: AppSpacing.lg), - // 持仓列表标题 + const SizedBox(height: 12), Text( - '持仓列表', - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurfaceVariant, + '\u2248 \$${_formatBalance(displayBalance)} USD', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: isDark ? AppColorScheme.darkOnSurfaceMuted : colorScheme.onSurfaceVariant, ), ), - SizedBox(height: AppSpacing.md), - if (sortedHoldings.isEmpty) - const _EmptyState(icon: LucideIcons.wallet, message: '暂无持仓') - else - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: sortedHoldings.length, - separatorBuilder: (_, __) => Container( - margin: EdgeInsets.only(left: 56), - height: 1, - color: AppColorScheme.glassPanelBorder, - ), - itemBuilder: (context, index) => _HoldingItem(holding: sortedHoldings[index]), - ), ], ), ); } + + String _calculateTradeTotal() { + double total = 0; + for (var h in provider.holdings) { + total += double.tryParse(h.currentValue?.toString() ?? '0') ?? 0; + } + return total.toStringAsFixed(2); + } + + String _formatBalance(String balance) { + final d = double.tryParse(balance) ?? 0; + return d.toStringAsFixed(2).replaceAllMapped( + RegExp(r'\B(?=(\d{3})+(?!\d))'), + (Match m) => ',', + ); + } } -/// 空状态 -class _EmptyState extends StatelessWidget { - final IconData icon; - final String message; +// ============================================ +// Action Buttons Row — .pen node pIpHe +// gap: 12, three buttons evenly distributed +// Each button: circle 48x48 fill $bg-tertiary, cornerRadius 24 +// icon: 20px $accent-primary (lucide: arrow-up-right / arrow-down-left / repeat) +// label: 12px w500 $text-secondary +// ============================================ - const _EmptyState({required this.icon, required this.message}); +class _ActionButtonsRow extends StatelessWidget { + final VoidCallback onDeposit; + final VoidCallback onWithdraw; + final VoidCallback onTransfer; + + const _ActionButtonsRow({ + 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; + final accentColor = isDark ? colorScheme.secondary : colorScheme.primary; + final bgColor = isDark ? colorScheme.surfaceContainerHighest : colorScheme.surfaceContainerHigh; + + return Row( + children: [ + _ActionButton( + icon: LucideIcons.arrowUpRight, + label: '充值', + accentColor: accentColor, + bgColor: bgColor, + onTap: onDeposit, + ), + const SizedBox(width: 12), + _ActionButton( + icon: LucideIcons.arrowDownLeft, + label: '提现', + accentColor: accentColor, + bgColor: bgColor, + onTap: onWithdraw, + ), + const SizedBox(width: 12), + _ActionButton( + icon: LucideIcons.repeat, + label: '划转', + accentColor: accentColor, + bgColor: bgColor, + onTap: onTransfer, + ), + ], + ); + } +} + +/// Single action button — matching .pen btn1/btn2/btn3 +class _ActionButton extends StatelessWidget { + final IconData icon; + final String label; + final Color accentColor; + final Color bgColor; + final VoidCallback onTap; + + const _ActionButton({ + required this.icon, + required this.label, + required this.accentColor, + required this.bgColor, + required this.onTap, + }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return Center( - child: Padding( - padding: EdgeInsets.all(AppSpacing.xl), + return Expanded( + child: GestureDetector( + onTap: onTap, child: Column( children: [ - Icon( - icon, - size: 48, - color: colorScheme.onSurfaceVariant, + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: bgColor, + shape: BoxShape.circle, + ), + alignment: Alignment.center, + child: Icon( + icon, + size: 20, + color: accentColor, + ), ), - SizedBox(height: AppSpacing.sm + AppSpacing.xs), + const SizedBox(height: 6), Text( - message, - style: TextStyle(color: colorScheme.onSurfaceVariant), + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, + ), ), ], ), @@ -500,77 +418,255 @@ class _EmptyState extends StatelessWidget { } } -/// 持仓项 -class _HoldingItem extends StatelessWidget { - final dynamic holding; +// ============================================ +// Records Link Row — .pen node fLHtq +// cornerRadius: lg, fill: $surface-card, padding: [14, 16], stroke: $border-default 1px +// recordsText: "充提记录" 14px w500 $text-primary +// recordsChevron: lucide chevron-right 16px $text-muted +// ============================================ - const _HoldingItem({required this.holding}); +class _RecordsLinkRow extends StatelessWidget { + final VoidCallback onTap; + + const _RecordsLinkRow({required this.onTap}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; + final mutedColor = isDark ? AppColorScheme.darkOnSurfaceMuted : colorScheme.onSurfaceVariant; + + return GestureDetector( + onTap: onTap, + child: GlassPanel( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14), + borderRadius: BorderRadius.circular(AppRadius.lg), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '充提记录', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, + ), + ), + Icon( + LucideIcons.chevronRight, + size: 16, + color: mutedColor, + ), + ], + ), + ), + ); + } +} + +// ============================================ +// Holdings Section — .pen nodes th9BG (header) + 6X6tC (card) +// Holdings Header: "交易账户持仓" 16px w600 $text-primary | "查看全部 >" 12px normal $text-secondary +// Holdings Card: cornerRadius lg, fill $surface-card, stroke $border-default 1px +// Each row: padding [14, 16], justifyContent space_between +// Left: avatar (36x36, cornerRadius 18, fill $accent-light) + coin info (gap 2) +// AvatarText: coinCode first letter, 14px w700 $accent-primary +// CoinName: 14px w600 $text-primary +// CoinAmt: 12px normal $text-secondary +// Right: value + pnl (gap 2, alignItems end) +// Value: 13px w500 $text-primary +// Pnl: 12px w500, $profit-green / $loss-red +// Divider: $border-default height 1 opacity 0.5 +// ============================================ + +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: [ + // Header row: "交易账户持仓" + "查看全部 >" + Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.sm), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '交易账户持仓', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + Text( + '查看全部 >', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + // Holdings card — uses real provider.holdings data + if (holdings.isEmpty) + Padding( + padding: const EdgeInsets.all(AppSpacing.xl), + child: Text( + '暂无持仓', + style: GoogleFonts.inter( + fontSize: 13, + color: colorScheme.onSurfaceVariant, + ), + ), + ) + else + GlassPanel( + padding: EdgeInsets.zero, + borderRadius: BorderRadius.circular(AppRadius.lg), + child: Column( + children: List.generate(holdings.length, (index) { + final h = holdings[index] as AccountTrade; + final isProfit = h.profitRate >= 0; + return Column( + children: [ + _HoldingRow( + coinCode: h.coinCode, + quantity: double.tryParse(h.quantity)?.toStringAsFixed(4) ?? h.quantity, + value: '${double.tryParse(h.currentValue)?.toStringAsFixed(2) ?? h.currentValue} USDT', + profitRate: '${isProfit ? '+' : ''}${h.profitRate.toStringAsFixed(2)}%', + isProfit: isProfit, + ), + if (index < holdings.length - 1) const _HoldingDivider(), + ], + ); + }), + ), + ), + ], + ); + } +} + +/// Divider between holding rows — .pen node BCCbR / yejhE +/// fill: $border-default, height: 1, opacity: 0.5 +class _HoldingDivider extends StatelessWidget { + const _HoldingDivider(); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Container( + height: 1, + margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + color: colorScheme.outlineVariant.withValues(alpha: 0.5), + ); + } +} + +/// Holding row — matching .pen nodes dAt4j / eK6vq / jiSUK +/// padding [14, 16], space_between layout +/// Left: avatar circle (36x36, radius 18, fill $accent-light) + coin info (gap 2) +/// Right: value + pnl (gap 2, align end) +class _HoldingRow extends StatelessWidget { + final String coinCode; + final String quantity; + final String value; + final String profitRate; + final bool isProfit; + + const _HoldingRow({ + required this.coinCode, + required this.quantity, + required this.value, + required this.profitRate, + required this.isProfit, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + final accentColor = isDark ? colorScheme.secondary : colorScheme.primary; + final accentBgColor = accentColor.withValues(alpha: 0.1); + final profitColor = isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.getDownColor(isDark); return Padding( - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm), + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14), child: Row( children: [ + // Avatar circle with first letter — .pen SJNDJ/EjSIN/3GQ5M Container( - width: 40, - height: 40, + width: 36, + height: 36, decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.md), + color: accentBgColor, + borderRadius: BorderRadius.circular(18), ), - child: Center( - child: Text( - holding.coinCode.substring(0, 1), - style: TextStyle( - color: colorScheme.primary, - fontWeight: FontWeight.bold, - ), + alignment: Alignment.center, + child: Text( + coinCode.substring(0, 1), + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w700, + color: accentColor, ), ), ), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), + const SizedBox(width: 10), + // Coin name + quantity — .pen fivxJ/Kxv3d/5CsoQ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ Text( - holding.coinCode, - style: GoogleFonts.spaceGrotesk( + coinCode, + style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), + const SizedBox(height: 2), Text( - '数量: ${holding.quantity}', - style: TextStyle( + quantity, + style: GoogleFonts.inter( fontSize: 12, + fontWeight: FontWeight.normal, color: colorScheme.onSurfaceVariant, ), ), ], ), ), + // Value + profit rate — .pen vYJsU/2nLAg/IlWck Column( crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, children: [ Text( - '${holding.currentValue} USDT', - style: TextStyle( - fontSize: 12, + value, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, color: colorScheme.onSurface, ), ), + const SizedBox(height: 2), Text( - holding.formattedProfitRate, - style: TextStyle( - color: holding.isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down, + profitRate, + style: GoogleFonts.inter( fontSize: 12, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w500, + color: profitColor, ), ), ], @@ -582,7 +678,7 @@ class _HoldingItem extends StatelessWidget { } // ============================================ -// Dialogs - Glass Panel 风格 +// Dialogs — kept from original with style updates // ============================================ void _showDepositDialog(BuildContext context) { @@ -596,7 +692,7 @@ void _showDepositDialog(BuildContext context) { backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.lg), - padding: EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -608,26 +704,25 @@ void _showDepositDialog(BuildContext context) { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Deposit (充值)', - style: GoogleFonts.spaceGrotesk( + '充值', + style: GoogleFonts.inter( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), - SizedBox(height: AppSpacing.xs), + const SizedBox(height: AppSpacing.xs), Text( 'Asset: USDT', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, - letterSpacing: 0.1, color: colorScheme.onSurfaceVariant, ), ), ], ), Container( - padding: EdgeInsets.all(AppSpacing.sm), + padding: const EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( color: colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.md), @@ -639,7 +734,7 @@ void _showDepositDialog(BuildContext context) { ), ], ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), ShadForm( key: formKey, child: ShadInputFormField( @@ -657,7 +752,7 @@ void _showDepositDialog(BuildContext context) { }, ), ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), Row( children: [ Expanded( @@ -669,7 +764,7 @@ void _showDepositDialog(BuildContext context) { showGlow: false, ), ), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), Expanded( child: NeonButton( text: '下一步', @@ -716,7 +811,7 @@ void _showDepositResultDialog(BuildContext context, Map data) { backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.lg), - padding: EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -728,55 +823,55 @@ void _showDepositResultDialog(BuildContext context, Map data) { color: AppColorScheme.getUpColor(isDark), size: 24, ), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), Text( '充值申请成功', - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), ], ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), _InfoRow(label: '订单号', value: orderNo), - SizedBox(height: AppSpacing.sm), + const SizedBox(height: AppSpacing.sm), _InfoRow(label: '充值金额', value: '$amount USDT', isBold: true), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), Text( '请向以下地址转账:', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, color: colorScheme.onSurfaceVariant, ), ), - SizedBox(height: AppSpacing.sm), + const SizedBox(height: AppSpacing.sm), _WalletAddressCard(address: walletAddress, network: walletNetwork), - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), Container( - padding: EdgeInsets.all(AppSpacing.sm), + padding: const EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( - color: AppColorScheme.warning.withOpacity(0.1), + color: AppColorScheme.warning.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all( - color: AppColorScheme.warning.withOpacity(0.2), + color: AppColorScheme.warning.withValues(alpha: 0.2), ), ), child: Row( children: [ Icon(Icons.info_outline, size: 16, color: AppColorScheme.warning), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), Expanded( child: Text( '转账完成后请点击"已打款"按钮确认', - style: TextStyle(fontSize: 12, color: AppColorScheme.warning), + style: GoogleFonts.inter(fontSize: 12, color: AppColorScheme.warning), ), ), ], ), ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), Row( children: [ Expanded( @@ -788,7 +883,7 @@ void _showDepositResultDialog(BuildContext context, Map data) { showGlow: false, ), ), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), Expanded( child: NeonButton( text: '已打款', @@ -833,14 +928,14 @@ class _InfoRow extends StatelessWidget { children: [ Text( label, - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, color: colorScheme.onSurfaceVariant, ), ), Text( value, - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: colorScheme.onSurface, @@ -862,12 +957,12 @@ class _WalletAddressCard extends StatelessWidget { final colorScheme = Theme.of(context).colorScheme; return Container( - padding: EdgeInsets.all(AppSpacing.md), + padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surfaceContainerHigh, borderRadius: BorderRadius.circular(AppRadius.md), border: Border.all( - color: colorScheme.outlineVariant.withOpacity(0.3), + color: colorScheme.outlineVariant.withValues(alpha: 0.3), ), ), child: Column( @@ -878,10 +973,9 @@ class _WalletAddressCard extends StatelessWidget { Expanded( child: Text( address, - style: TextStyle( + style: const TextStyle( fontFamily: 'monospace', fontSize: 12, - color: colorScheme.onSurface, ), ), ), @@ -891,9 +985,9 @@ class _WalletAddressCard extends StatelessWidget { ToastUtils.show('地址已复制到剪贴板'); }, child: Container( - padding: EdgeInsets.all(AppSpacing.xs), + padding: const EdgeInsets.all(AppSpacing.xs), decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), + color: colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Icon( @@ -905,10 +999,10 @@ class _WalletAddressCard extends StatelessWidget { ), ], ), - SizedBox(height: AppSpacing.sm), + const SizedBox(height: AppSpacing.sm), Text( '网络: $network', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 11, color: colorScheme.onSurfaceVariant, ), @@ -932,7 +1026,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.lg), - padding: EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.all(AppSpacing.lg), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, @@ -941,9 +1035,9 @@ void _showWithdrawDialog(BuildContext context, String? balance) { Row( children: [ Container( - padding: EdgeInsets.all(AppSpacing.sm), + padding: const EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), + color: colorScheme.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Icon( @@ -951,37 +1045,37 @@ void _showWithdrawDialog(BuildContext context, String? balance) { color: colorScheme.primary, ), ), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), Text( - '提现 (Withdraw)', - style: GoogleFonts.spaceGrotesk( + '提现', + style: GoogleFonts.inter( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), ], ), - SizedBox(height: AppSpacing.xs), + const SizedBox(height: AppSpacing.xs), Text( - 'Securely transfer your assets to an external wallet address.', - style: TextStyle( + '安全地将您的资产转移到外部钱包地址', + style: GoogleFonts.inter( fontSize: 12, color: colorScheme.onSurfaceVariant, ), ), if (balance != null) ...[ - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), Container( - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: AppSpacing.md, vertical: AppSpacing.sm, ), decoration: BoxDecoration( - color: AppColorScheme.up.withOpacity(0.1), + color: AppColorScheme.up.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadius.full), border: Border.all( - color: AppColorScheme.up.withOpacity(0.2), + color: AppColorScheme.up.withValues(alpha: 0.2), ), ), child: Row( @@ -989,15 +1083,14 @@ void _showWithdrawDialog(BuildContext context, String? balance) { children: [ Text( '可用余额: ', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 10, - letterSpacing: 0.1, color: colorScheme.onSurfaceVariant, ), ), Text( '$balance USDT', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.bold, color: AppColorScheme.up, @@ -1007,7 +1100,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { ), ), ], - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), ShadForm( key: formKey, child: Column( @@ -1020,7 +1113,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), ShadInputFormField( id: 'address', controller: addressController, @@ -1028,7 +1121,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { placeholder: const Text('请输入提现地址'), validator: (v) => Validators.required(v, '提现地址'), ), - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), ShadInputFormField( id: 'contact', controller: contactController, @@ -1038,7 +1131,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { ], ), ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), Row( children: [ Expanded( @@ -1050,7 +1143,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { showGlow: false, ), ), - SizedBox(width: AppSpacing.sm), + const SizedBox(width: AppSpacing.sm), Expanded( child: NeonButton( text: '提交', @@ -1080,22 +1173,21 @@ void _showWithdrawDialog(BuildContext context, String? balance) { ), ], ), - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.verified_user, size: 12, - color: colorScheme.onSurfaceVariant.withOpacity(0.5), + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), ), - SizedBox(width: AppSpacing.xs), + const SizedBox(width: AppSpacing.xs), Text( 'End-to-End Encrypted Transaction', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 10, - letterSpacing: 0.1, - color: colorScheme.onSurfaceVariant.withOpacity(0.5), + color: colorScheme.onSurfaceVariant.withValues(alpha: 0.5), ), ), ], @@ -1113,7 +1205,6 @@ void _navigateToTransfer(BuildContext context) async { context, MaterialPageRoute(builder: (_) => const TransferPage()), ); - // 如果划转成功,刷新数据 if (result == true && context.mounted) { context.read().refreshAll(force: true); } @@ -1128,23 +1219,27 @@ void _showResultDialog(BuildContext context, String title, String? message) { backgroundColor: Colors.transparent, child: GlassPanel( borderRadius: BorderRadius.circular(AppRadius.lg), - padding: EdgeInsets.all(AppSpacing.lg), + padding: const EdgeInsets.all(AppSpacing.lg), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(title, - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - )), + Text( + title, + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, + ), + ), if (message != null) ...[ - SizedBox(height: AppSpacing.sm), - Text(message, - style: TextStyle(color: colorScheme.onSurfaceVariant), - textAlign: TextAlign.center), + const SizedBox(height: AppSpacing.sm), + Text( + message, + style: GoogleFonts.inter(color: colorScheme.onSurfaceVariant), + textAlign: TextAlign.center, + ), ], - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.lg), SizedBox( width: double.infinity, child: NeonButton( @@ -1161,4 +1256,3 @@ void _showResultDialog(BuildContext context, String title, String? message) { ), ); } - diff --git a/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart b/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart index 72fbc11..124f5d9 100644 --- a/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart +++ b/flutter_monisuo/lib/ui/pages/asset/transfer_page.dart @@ -1,16 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../providers/asset_provider.dart'; import '../../../data/models/account_models.dart'; -import '../../shared/ui_constants.dart'; -import '../../components/neon_glow.dart'; -/// 划转页面 - 币安风格 +/// 划转页面 class TransferPage extends StatefulWidget { const TransferPage({super.key}); @@ -20,6 +18,7 @@ class TransferPage extends StatefulWidget { class _TransferPageState extends State { final _amountController = TextEditingController(); + final _focusNode = FocusNode(); int _direction = 1; // 1: 资金→交易, 2: 交易→资金 bool _isLoading = false; @@ -34,6 +33,7 @@ class _TransferPageState extends State { @override void dispose() { _amountController.dispose(); + _focusNode.dispose(); super.dispose(); } @@ -124,7 +124,6 @@ class _TransferPageState extends State { void _setQuickAmount(double percent) { final available = double.tryParse(_availableBalance) ?? 0; final amount = available * percent; - // 保留8位小数,去除末尾0 _amountController.text = amount.toStringAsFixed(8).replaceAll(RegExp(r'\.?0+$'), ''); } @@ -140,21 +139,36 @@ class _TransferPageState extends State { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; + // Theme-aware colors matching .pen design tokens + final bgSecondary = isDark ? const Color(0xFF0B1120) : const Color(0xFFF8FAFC); + final surfaceCard = isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF); + final bgTertiary = isDark ? const Color(0xFF1E293B) : const Color(0xFFF1F5F9); + final borderDefault = isDark ? const Color(0xFF334155) : const Color(0xFFE2E8F0); + final textPrimary = isDark ? const Color(0xFFF8FAFC) : const Color(0xFF0F172A); + final textSecondary = isDark ? const Color(0xFF94A3B8) : const Color(0xFF475569); + final textMuted = isDark ? const Color(0xFF64748B) : const Color(0xFF94A3B8); + final textInverse = isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF); + final accentPrimary = isDark ? const Color(0xFFD4AF37) : const Color(0xFF1F2937); + final goldAccent = isDark ? const Color(0xFFD4AF37) : const Color(0xFFF59E0B); + final profitGreen = isDark ? const Color(0xFF4ADE80) : const Color(0xFF16A34A); + final profitGreenBg = isDark ? const Color(0xFF052E16) : const Color(0xFFF0FDF4); + return Scaffold( - backgroundColor: colorScheme.background, + backgroundColor: bgSecondary, appBar: AppBar( - backgroundColor: Colors.transparent, + backgroundColor: isDark ? const Color(0xFF0F172A) : const Color(0xFFFFFFFF), elevation: 0, + scrolledUnderElevation: 0, leading: IconButton( - icon: Icon(Icons.arrow_back, color: colorScheme.onSurface), + icon: Icon(LucideIcons.arrowLeft, color: textPrimary, size: 20), onPressed: () => Navigator.of(context).pop(), ), title: Text( - '资金划转', - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, + '账户划转', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textPrimary, ), ), centerTitle: true, @@ -162,135 +176,49 @@ class _TransferPageState extends State { body: Consumer( builder: (context, provider, _) { return SingleChildScrollView( - padding: AppSpacing.pagePadding, + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), child: Column( children: [ - // 第一个卡片位置 - 带动画 - TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - builder: (context, value, child) { - return AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - transitionBuilder: (widget, animation) { - return SlideTransition( - position: Tween( - begin: const Offset(0, -1), - end: Offset.zero, - ).animate(animation), - child: FadeTransition( - opacity: animation, - child: widget, - ), - ); - }, - child: _direction == 1 - ? _buildAccountCard( - key: const ValueKey('from-card'), - label: '从', - accountName: _fromLabel, - balance: _fromBalance, - isDark: isDark, - colorScheme: colorScheme, - ) - : _buildAccountCard( - key: const ValueKey('to-card-top'), - label: '到', - accountName: _toLabel, - balance: _toBalance, - isDark: isDark, - colorScheme: colorScheme, - ), - ); - }, + // --- Transfer Direction Card --- + _buildTransferDirectionCard( + colorScheme: colorScheme, + isDark: isDark, + surfaceCard: surfaceCard, + borderDefault: borderDefault, + textPrimary: textPrimary, + textSecondary: textSecondary, + textMuted: textMuted, + textInverse: textInverse, + accentPrimary: accentPrimary, ), - // 方向切换按钮(固定在中间) - GestureDetector( - onTap: _toggleDirection, - child: Container( - margin: EdgeInsets.symmetric(vertical: AppSpacing.sm), - padding: EdgeInsets.all(AppSpacing.sm + AppSpacing.xs), - decoration: BoxDecoration( - color: colorScheme.primary, - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: colorScheme.primary.withOpacity(0.3), - blurRadius: 8, - ), - ], - ), - child: Icon( - Icons.swap_vert, - color: colorScheme.onPrimary, - size: 22, - ), - ), + const SizedBox(height: 24), + + // --- Amount Section --- + _buildAmountSection( + isDark: isDark, + bgTertiary: bgTertiary, + textPrimary: textPrimary, + textSecondary: textSecondary, + textMuted: textMuted, + goldAccent: goldAccent, ), - // 第二个卡片位置 - 带动画 - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - transitionBuilder: (widget, animation) { - return SlideTransition( - position: Tween( - begin: const Offset(0, 1), - end: Offset.zero, - ).animate(animation), - child: FadeTransition( - opacity: animation, - child: widget, - ), - ); - }, - child: _direction == 1 - ? _buildAccountCard( - key: const ValueKey('to-card'), - label: '到', - accountName: _toLabel, - balance: _toBalance, - isDark: isDark, - colorScheme: colorScheme, - ) - : _buildAccountCard( - key: const ValueKey('from-card-bottom'), - label: '从', - accountName: _fromLabel, - balance: _fromBalance, - isDark: isDark, - colorScheme: colorScheme, - ), + const SizedBox(height: 24), + + // --- Tips Card --- + _buildTipsCard( + profitGreen: profitGreen, + profitGreenBg: profitGreenBg, ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: 24), - // 金额输入卡片 - _buildAmountSection(colorScheme, isDark), - - SizedBox(height: AppSpacing.lg), - - // 确认按钮 - SizedBox( - width: double.infinity, - child: NeonButton( - text: _isLoading ? '处理中...' : '确认划转', - icon: _isLoading ? null : LucideIcons.arrowRightLeft, - type: NeonButtonType.primary, - onPressed: _isLoading ? null : _doTransfer, - height: 52, - showGlow: true, - ), + // --- Confirm Button --- + _buildConfirmButton( + accentPrimary: accentPrimary, + textInverse: textInverse, ), - - SizedBox(height: AppSpacing.md), - - // 划转说明 - _buildTips(colorScheme), ], ), ); @@ -299,275 +227,345 @@ class _TransferPageState extends State { ); } - /// 账户卡片 - Widget _buildAccountCard({ + /// Transfer direction card with source, swap, destination + Widget _buildTransferDirectionCard({ + required ColorScheme colorScheme, + required bool isDark, + required Color surfaceCard, + required Color borderDefault, + required Color textPrimary, + required Color textSecondary, + required Color textMuted, + required Color textInverse, + required Color accentPrimary, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: surfaceCard, + borderRadius: BorderRadius.circular(AppRadius.xl), + border: Border.all(color: borderDefault.withOpacity(0.6)), + ), + child: Column( + children: [ + // Source account + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + transitionBuilder: (widget, animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0, -1), + end: Offset.zero, + ).animate(animation), + child: FadeTransition(opacity: animation, child: widget), + ); + }, + child: _buildAccountRow( + key: ValueKey('src-$_direction'), + label: '从', + accountName: _fromLabel, + balance: _fromBalance, + isDark: isDark, + textMuted: textMuted, + textPrimary: textPrimary, + textSecondary: textSecondary, + ), + ), + + // Swap button + GestureDetector( + onTap: _toggleDirection, + child: Container( + width: 36, + height: 36, + margin: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: accentPrimary, + shape: BoxShape.circle, + ), + child: Center( + child: Icon( + LucideIcons.arrowUpDown, + size: 18, + color: textInverse, + ), + ), + ), + ), + + // Destination account + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + transitionBuilder: (widget, animation) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(animation), + child: FadeTransition(opacity: animation, child: widget), + ); + }, + child: _buildAccountRow( + key: ValueKey('dst-$_direction'), + label: '到', + accountName: _toLabel, + balance: _toBalance, + isDark: isDark, + textMuted: textMuted, + textPrimary: textPrimary, + textSecondary: textSecondary, + ), + ), + ], + ), + ); + } + + /// Single account row inside the direction card + Widget _buildAccountRow({ Key? key, required String label, required String accountName, required String balance, required bool isDark, - required ColorScheme colorScheme, + required Color textMuted, + required Color textPrimary, + required Color textSecondary, }) { return Container( key: key, width: double.infinity, - padding: EdgeInsets.all(AppSpacing.md), - decoration: BoxDecoration( - color: isDark ? colorScheme.surfaceContainer : colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(AppRadius.lg), - border: Border.all( - color: colorScheme.outlineVariant.withOpacity(0.15), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs), - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - child: Text( - label, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: colorScheme.primary, - ), - ), - ), - SizedBox(width: AppSpacing.sm), - Text( - accountName, - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - ], - ), - SizedBox(height: AppSpacing.sm), - Row( - children: [ - Text( - '可用余额', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - SizedBox(width: AppSpacing.xs), - Text( - '$balance USDT', - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - ], - ), - ], - ), - ); - } - - /// 金额输入区域 - Widget _buildAmountSection(ColorScheme colorScheme, bool isDark) { - return Container( - width: double.infinity, - padding: EdgeInsets.all(AppSpacing.md), - decoration: BoxDecoration( - color: isDark ? colorScheme.surfaceContainer : colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(AppRadius.lg), - border: Border.all( - color: colorScheme.outlineVariant.withOpacity(0.15), - ), - ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Label row Text( - '划转金额', - style: TextStyle( - fontSize: 13, - color: colorScheme.onSurfaceVariant, + label, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, + color: textMuted, ), ), - SizedBox(height: AppSpacing.sm), - // 金额输入行 + const SizedBox(height: 8), + // Account name + balance row Row( - crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: TextField( - controller: _amountController, - keyboardType: const TextInputType.numberWithOptions(decimal: true), - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')), - ], - style: GoogleFonts.spaceGrotesk( - fontSize: 18, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - decoration: InputDecoration( - hintText: '0.00', - hintStyle: GoogleFonts.spaceGrotesk( - fontSize: 18, - fontWeight: FontWeight.bold, - color: colorScheme.onSurfaceVariant.withOpacity(0.3), - ), - border: InputBorder.none, - contentPadding: EdgeInsets.zero, - isDense: true, - ), - ), - ), - Padding( - padding: EdgeInsets.only(bottom: 4), - child: Text( - 'USDT', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ], - ), - SizedBox(height: AppSpacing.sm), - // 快捷按钮行 - Row( - children: [ - Text( - '可用: ${_availableBalance}', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - Spacer(), - _buildQuickButton('25%', 0.25, colorScheme), - SizedBox(width: AppSpacing.xs), - _buildQuickButton('50%', 0.50, colorScheme), - SizedBox(width: AppSpacing.xs), - _buildQuickButton('75%', 0.75, colorScheme), - SizedBox(width: AppSpacing.xs), - _buildQuickButton('全部', 1.0, colorScheme), - ], - ), - if (_direction == 2) ...[ - SizedBox(height: AppSpacing.sm), - Container( - padding: EdgeInsets.all(AppSpacing.sm), - decoration: BoxDecoration( - color: AppColorScheme.warning.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - child: Row( + // Account name with icon + Row( children: [ Icon( - LucideIcons.triangleAlert, - size: 14, - color: AppColorScheme.warning, + label == '从' ? LucideIcons.wallet : LucideIcons.repeat, + size: 18, + color: textSecondary, ), - SizedBox(width: AppSpacing.xs), - Expanded( - child: Text( - '仅支持 USDT 资产划转到资金账户', - style: TextStyle( - fontSize: 11, - color: AppColorScheme.warning, - ), + const SizedBox(width: 10), + Text( + accountName, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: textPrimary, ), ), ], ), + // Balance + Text( + '\u00A5 ${_formatBalance(balance)}', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: textPrimary, + ), + ), + ], + ), + ], + ), + ); + } + + /// Format balance for display + String _formatBalance(String balance) { + final val = double.tryParse(balance); + if (val == null) return '0.00'; + return val.toStringAsFixed(2); + } + + /// Amount input section + Widget _buildAmountSection({ + required bool isDark, + required Color bgTertiary, + required Color textPrimary, + required Color textSecondary, + required Color textMuted, + required Color goldAccent, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Label row: "划转金额" + "全部划转" + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '划转金额', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: textSecondary, + ), + ), + GestureDetector( + onTap: () => _setQuickAmount(1.0), + child: Text( + '全部划转', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: goldAccent, + ), + ), ), ], - ], - ), - ); - } - - /// 快捷百分比按钮 - Widget _buildQuickButton(String label, double percent, ColorScheme colorScheme) { - return GestureDetector( - onTap: () => _setQuickAmount(percent), - child: Container( - padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs), - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.sm), ), - child: Text( - label, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: colorScheme.primary, - ), - ), - ), - ); - } + const SizedBox(height: 12), - /// 划转说明 - Widget _buildTips(ColorScheme colorScheme) { - return Container( - padding: EdgeInsets.all(AppSpacing.md), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(AppRadius.md), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '划转说明', - style: GoogleFonts.spaceGrotesk( - fontSize: 12, - fontWeight: FontWeight.w600, - color: colorScheme.onSurfaceVariant, - ), - ), - SizedBox(height: AppSpacing.sm), - _buildTipItem('资金账户用于充提,交易账户用于买卖币种', colorScheme), - _buildTipItem('划转操作即时到账,不可撤销', colorScheme), - _buildTipItem('交易账户只有 USDT 可直接划转到资金账户', colorScheme), - _buildTipItem('其他币种需先卖出换成 USDT 后才能划转', colorScheme), - ], - ), - ); - } - - Widget _buildTipItem(String text, ColorScheme colorScheme) { - return Padding( - padding: EdgeInsets.only(bottom: AppSpacing.xs), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 4, - height: 4, - margin: EdgeInsets.only(top: 6, right: AppSpacing.sm), + // Amount input field + GestureDetector( + onTap: () => _focusNode.requestFocus(), + child: Container( + width: double.infinity, + height: 56, + padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: colorScheme.onSurfaceVariant, - shape: BoxShape.circle, + color: bgTertiary, + borderRadius: BorderRadius.circular(AppRadius.lg), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Input + Expanded( + child: TextField( + controller: _amountController, + focusNode: _focusNode, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,8}')), + ], + style: GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.w700, + color: textPrimary, + ), + decoration: InputDecoration( + hintText: '0.00', + hintStyle: GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.w700, + color: textMuted, + ), + border: InputBorder.none, + contentPadding: EdgeInsets.zero, + isDense: true, + ), + ), + ), + // Suffix + Padding( + padding: const EdgeInsets.only(left: 8), + child: Text( + 'USDT', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.normal, + color: textMuted, + ), + ), + ), + ], ), ), + ), + const SizedBox(height: 12), + + // Percent buttons + Row( + children: [ + _buildPercentButton('25%', 0.25, isDark, bgTertiary, textSecondary), + const SizedBox(width: 8), + _buildPercentButton('50%', 0.50, isDark, bgTertiary, textSecondary), + const SizedBox(width: 8), + _buildPercentButton('75%', 0.75, isDark, bgTertiary, textSecondary), + const SizedBox(width: 8), + _buildPercentButton('100%', 1.0, isDark, bgTertiary, textSecondary), + ], + ), + ], + ); + } + + /// Percent quick button + Widget _buildPercentButton(String label, double percent, bool isDark, Color bgTertiary, Color textSecondary) { + return Expanded( + child: GestureDetector( + onTap: () => _setQuickAmount(percent), + child: Container( + height: 36, + decoration: BoxDecoration( + color: bgTertiary, + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Center( + child: Text( + label, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: textSecondary, + ), + ), + ), + ), + ), + ); + } + + /// Tips card with green background + Widget _buildTipsCard({ + required Color profitGreen, + required Color profitGreenBg, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: profitGreenBg, + borderRadius: BorderRadius.circular(AppRadius.lg), + ), + child: Row( + children: [ + Icon( + LucideIcons.info, + size: 16, + color: profitGreen, + ), + const SizedBox(width: 8), Expanded( child: Text( - text, - style: TextStyle( - fontSize: 11, - color: colorScheme.onSurfaceVariant, + '划转即时到账,无需手续费', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: profitGreen, ), ), ), @@ -575,4 +573,43 @@ class _TransferPageState extends State { ), ); } + + /// Confirm button + Widget _buildConfirmButton({ + required Color accentPrimary, + required Color textInverse, + }) { + return SizedBox( + width: double.infinity, + height: 52, + child: GestureDetector( + onTap: _isLoading ? null : _doTransfer, + child: Container( + decoration: BoxDecoration( + color: accentPrimary, + borderRadius: BorderRadius.circular(AppRadius.lg), + ), + child: Center( + child: _isLoading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(textInverse), + ), + ) + : Text( + '确认划转', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w700, + color: textInverse, + ), + ), + ), + ), + ), + ); + } } diff --git a/flutter_monisuo/lib/ui/pages/auth/login_page.dart b/flutter_monisuo/lib/ui/pages/auth/login_page.dart index 91a5629..4c695db 100644 --- a/flutter_monisuo/lib/ui/pages/auth/login_page.dart +++ b/flutter_monisuo/lib/ui/pages/auth/login_page.dart @@ -2,6 +2,7 @@ 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 '../../../core/theme/app_spacing.dart'; import '../../../providers/auth_provider.dart'; import '../main/main_page.dart'; @@ -16,38 +17,52 @@ class LoginPage extends StatefulWidget { class _LoginPageState extends State { final formKey = GlobalKey(); + final _usernameController = TextEditingController(); + final _passwordController = TextEditingController(); + bool _obscurePassword = true; - static const _maxFormWidth = 400.0; - static const _logoSize = 64.0; static const _loadingIndicatorSize = 16.0; + static const _logoCircleSize = 80.0; + static const _inputHeight = 52.0; + static const _buttonHeight = 52.0; + + /// 设计稿 radius-lg = 14 + static const _designRadiusLg = 14.0; + + @override + void dispose() { + _usernameController.dispose(); + _passwordController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); + final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: _maxFormWidth), - child: Padding( - padding: EdgeInsets.all(AppSpacing.lg), - child: ShadForm( - key: formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildHeader(theme), - SizedBox(height: AppSpacing.xxl), - _buildUsernameField(), - SizedBox(height: AppSpacing.md), - _buildPasswordField(), - SizedBox(height: AppSpacing.lg), - _buildLoginButton(), - SizedBox(height: AppSpacing.md), - _buildRegisterLink(theme), - ], - ), + backgroundColor: isDark + ? AppColorScheme.darkBackground + : AppColorScheme.lightSurface, + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xl, + vertical: AppSpacing.xxl, + ), + child: ShadForm( + key: formKey, + child: Column( + children: [ + // 顶部品牌区域 + _buildBrandSection(isDark), + const SizedBox(height: AppSpacing.xxl), + // 表单区域 + _buildFormSection(isDark), + const SizedBox(height: AppSpacing.xl), + // 底部注册链接 + _buildRegisterRow(isDark), + ], ), ), ), @@ -55,87 +70,258 @@ class _LoginPageState extends State { ); } - Widget _buildHeader(ShadThemeData theme) { + // ============================================ + // 品牌区域 - Logo + 品牌名 + 标语 + // ============================================ + + Widget _buildBrandSection(bool isDark) { return Column( children: [ - Icon( - LucideIcons.trendingUp, - size: _logoSize, - color: theme.colorScheme.primary, + // Logo 圆形:渐变 #1F2937 → #374151,内含 "M" + Container( + width: _logoCircleSize, + height: _logoCircleSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF1F2937), Color(0xFF374151)], + ), + ), + alignment: Alignment.center, + child: Text( + 'M', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w800, + color: isDark + ? AppColorScheme.darkOnSurface + : Colors.white, + ), + ), ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.md), + // 品牌名 "MONISUO" Text( - '模拟所', - style: theme.textTheme.h1, + 'MONISUO', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.w800, + letterSpacing: 3, + color: isDark + ? AppColorScheme.darkOnSurface + : AppColorScheme.lightOnSurface, + ), textAlign: TextAlign.center, ), - SizedBox(height: AppSpacing.sm), + const SizedBox(height: AppSpacing.md), + // 标语 Text( '虚拟货币模拟交易平台', - style: theme.textTheme.muted, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.normal, + color: isDark + ? AppColorScheme.darkOnSurfaceVariant + : AppColorScheme.lightOnSurfaceVariant, + ), textAlign: TextAlign.center, ), ], ); } - Widget _buildUsernameField() { - return ShadInputFormField( - id: 'username', - label: const Text('用户名'), - placeholder: const Text('请输入用户名'), - leading: const Icon(LucideIcons.user), - validator: _validateUsername, + // ============================================ + // 表单区域 - 用户名 + 密码 + 登录按钮 + // ============================================ + + Widget _buildFormSection(bool isDark) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildUsernameField(isDark), + const SizedBox(height: AppSpacing.md), + _buildPasswordField(isDark), + const SizedBox(height: AppSpacing.sm), + _buildLoginButton(isDark), + ], ); } - Widget _buildPasswordField() { - return ShadInputFormField( - id: 'password', - label: const Text('密码'), - placeholder: const Text('请输入密码'), - obscureText: true, - leading: const Icon(LucideIcons.lock), - validator: _validatePassword, + Widget _buildUsernameField(bool isDark) { + final borderColor = isDark + ? AppColorScheme.darkOutlineVariant + : AppColorScheme.lightOutlineVariant; + final cardColor = isDark + ? AppColorScheme.darkSurfaceContainer + : AppColorScheme.lightSurfaceLowest; + final iconColor = isDark + ? AppColorScheme.darkOnSurfaceMuted + : AppColorScheme.lightOnSurfaceMuted; + + return SizedBox( + height: _inputHeight, + child: ShadInputFormField( + id: 'username', + placeholder: const Text('请输入用户名'), + leading: Padding( + padding: const EdgeInsets.only(right: AppSpacing.sm), + child: Icon(LucideIcons.user, size: 18, color: iconColor), + ), + validator: _validateUsername, + controller: _usernameController, + decoration: ShadDecoration( + border: ShadBorder.all( + color: borderColor, + radius: BorderRadius.circular(_designRadiusLg), + ), + ), + style: TextStyle( + fontSize: 14, + color: isDark + ? AppColorScheme.darkOnSurface + : AppColorScheme.lightOnSurface, + ), + ), ); } - Widget _buildLoginButton() { + Widget _buildPasswordField(bool isDark) { + final borderColor = isDark + ? AppColorScheme.darkOutlineVariant + : AppColorScheme.lightOutlineVariant; + final iconColor = isDark + ? AppColorScheme.darkOnSurfaceMuted + : AppColorScheme.lightOnSurfaceMuted; + + return SizedBox( + height: _inputHeight, + child: ShadInputFormField( + id: 'password', + placeholder: const Text('请输入密码'), + obscureText: _obscurePassword, + leading: Padding( + padding: const EdgeInsets.only(right: AppSpacing.sm), + child: Icon(LucideIcons.lock, size: 18, color: iconColor), + ), + trailing: GestureDetector( + onTap: () => setState(() => _obscurePassword = !_obscurePassword), + child: Icon( + _obscurePassword ? LucideIcons.eyeOff : LucideIcons.eye, + size: 18, + color: iconColor, + ), + ), + validator: _validatePassword, + controller: _passwordController, + decoration: ShadDecoration( + border: ShadBorder.all( + color: borderColor, + radius: BorderRadius.circular(_designRadiusLg), + ), + ), + style: TextStyle( + fontSize: 14, + color: isDark + ? AppColorScheme.darkOnSurface + : AppColorScheme.lightOnSurface, + ), + ), + ); + } + + Widget _buildLoginButton(bool isDark) { + // 设计稿: accent-primary = light:#1F2937 / dark:#D4AF37 + final buttonColor = isDark + ? AppColorScheme.darkSecondary + : const Color(0xFF1F2937); + final textColor = isDark + ? AppColorScheme.darkBackground + : Colors.white; + return Consumer( builder: (context, auth, _) { - return ShadButton( - onPressed: auth.isLoading ? null : () => _handleLogin(auth), - child: auth.isLoading - ? const SizedBox.square( - dimension: _loadingIndicatorSize, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, + return SizedBox( + height: _buttonHeight, + child: ShadButton( + onPressed: auth.isLoading ? null : () => _handleLogin(auth), + backgroundColor: buttonColor, + foregroundColor: textColor, + decoration: ShadDecoration( + border: ShadBorder.all( + radius: BorderRadius.circular(_designRadiusLg), + ), + ), + child: auth.isLoading + ? SizedBox.square( + dimension: _loadingIndicatorSize, + child: CircularProgressIndicator( + strokeWidth: 2, + color: textColor, + ), + ) + : Text( + '登录', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: textColor, + ), ), - ) - : const Text('登录'), + ), ); }, ); } - Widget _buildRegisterLink(ShadThemeData theme) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '还没有账号?', - style: theme.textTheme.muted, - ), - ShadButton.link( - onPressed: _navigateToRegister, - child: const Text('立即注册'), - ), - ], + // ============================================ + // 底部注册链接 + // ============================================ + + Widget _buildRegisterRow(bool isDark) { + // gold-accent: light=#F59E0B / dark=#D4AF37 + final goldColor = isDark + ? AppColorScheme.darkSecondary + : const Color(0xFFF59E0B); + final secondaryTextColor = isDark + ? AppColorScheme.darkOnSurfaceVariant + : AppColorScheme.lightOnSurfaceVariant; + + return Padding( + padding: const EdgeInsets.only(bottom: AppSpacing.xl), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '还没有账户?', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.normal, + color: secondaryTextColor, + ), + ), + const SizedBox(width: AppSpacing.xs), + GestureDetector( + onTap: _navigateToRegister, + child: Text( + '立即注册', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: goldColor, + ), + ), + ), + ], + ), ); } + // ============================================ // Validators + // ============================================ + String? _validateUsername(String? value) { if (value == null || value.isEmpty) { return '请输入用户名'; @@ -156,7 +342,10 @@ class _LoginPageState extends State { return null; } + // ============================================ // Actions + // ============================================ + Future _handleLogin(AuthProvider auth) async { if (!formKey.currentState!.saveAndValidate()) return; @@ -176,7 +365,6 @@ class _LoginPageState extends State { } void _navigateToMainPage() { - // 使用 Navigator 跳转到主页面,替换当前页面 Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => const MainPage()), (route) => false, diff --git a/flutter_monisuo/lib/ui/pages/home/header_bar.dart b/flutter_monisuo/lib/ui/pages/home/header_bar.dart new file mode 100644 index 0000000..5e0da98 --- /dev/null +++ b/flutter_monisuo/lib/ui/pages/home/header_bar.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../../providers/auth_provider.dart'; + +/// 首页顶栏 - Logo + 搜索/通知/头像 +class HeaderBar extends StatelessWidget { + const HeaderBar({super.key}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + children: [ + // Logo + Text( + 'MONISUO', + style: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w700, + letterSpacing: 1, + color: colorScheme.onSurface, + ), + ), + const Spacer(), + // Search button + _IconButton( + icon: LucideIcons.search, + colorScheme: colorScheme, + onTap: () {}, + ), + const SizedBox(width: 8), + // Bell button + _IconButton( + icon: LucideIcons.bell, + colorScheme: colorScheme, + onTap: () {}, + ), + const SizedBox(width: 8), + // Avatar + Consumer( + builder: (context, auth, _) { + final username = auth.user?.username ?? ''; + final initial = username.isNotEmpty ? username[0].toUpperCase() : '?'; + return Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: colorScheme.primary, + shape: BoxShape.circle, + ), + alignment: Alignment.center, + child: Text( + initial, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ); + }, + ), + ], + ), + ); + } +} + +class _IconButton extends StatelessWidget { + const _IconButton({ + required this.icon, + required this.colorScheme, + required this.onTap, + }); + + final IconData icon; + final ColorScheme colorScheme; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(16), + ), + alignment: Alignment.center, + child: Icon( + icon, + size: 16, + color: colorScheme.onSurfaceVariant, + ), + ), + ); + } +} diff --git a/flutter_monisuo/lib/ui/pages/home/home_page.dart b/flutter_monisuo/lib/ui/pages/home/home_page.dart index 2f4b497..988c231 100644 --- a/flutter_monisuo/lib/ui/pages/home/home_page.dart +++ b/flutter_monisuo/lib/ui/pages/home/home_page.dart @@ -18,6 +18,9 @@ import '../../components/glass_panel.dart'; import '../../components/neon_glow.dart'; import '../main/main_page.dart'; import '../mine/welfare_center_page.dart'; +import 'header_bar.dart'; +import 'quick_actions_row.dart'; +import 'hot_coins_section.dart'; /// 首页 class HomePage extends StatefulWidget { @@ -102,8 +105,8 @@ class _HomePageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 问候 - _GreetingSection(), + // Header + HeaderBar(), SizedBox(height: AppSpacing.md), // 资产卡片(含总盈利 + 可折叠盈亏日历) _AssetCard( @@ -111,6 +114,15 @@ class _HomePageState extends State onDeposit: _showDeposit, ), SizedBox(height: AppSpacing.md), + // 快捷操作栏 + QuickActionsRow( + onDeposit: _showDeposit, + onWithdraw: () => _navigateToAssetPage(), + onTransfer: () => _navigateToAssetPage(), + onProfit: () {}, + onBills: () => _navigateToAssetPage(), + ), + SizedBox(height: AppSpacing.md), // 福利中心入口卡片 _WelfareCard( totalClaimable: _totalClaimable, @@ -120,6 +132,9 @@ class _HomePageState extends State ), ), SizedBox(height: AppSpacing.lg), + // 热门币种 + HotCoinsSection(), + SizedBox(height: AppSpacing.lg), // 持仓 _HoldingsSection(holdings: provider.holdings), ], @@ -416,40 +431,7 @@ class _HomePageState extends State } } -/// 问候区域 -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, - ), - ), - SizedBox(height: AppSpacing.xs), - Text( - auth.user?.username ?? '用户', - style: TextStyle( - color: colorScheme.onSurface, - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), - ], - ); - }, - ); - } -} - +/// Header 栏:品牌名 + 搜索/通知/头像 /// 资产卡片(含总盈利 + 可折叠盈亏日历) class _AssetCard extends StatefulWidget { final AssetOverview? overview; diff --git a/flutter_monisuo/lib/ui/pages/home/hot_coins_section.dart b/flutter_monisuo/lib/ui/pages/home/hot_coins_section.dart new file mode 100644 index 0000000..09d059b --- /dev/null +++ b/flutter_monisuo/lib/ui/pages/home/hot_coins_section.dart @@ -0,0 +1,199 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../../core/theme/app_color_scheme.dart'; +import '../../../core/theme/app_spacing.dart'; + +/// 首页热门币种区块 +class HotCoinsSection extends StatelessWidget { + const HotCoinsSection({super.key}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Column( + children: [ + // Title row + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '热门币种', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + Text( + '更多', + style: GoogleFonts.inter( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + const SizedBox(height: 12), + // Card + Container( + decoration: BoxDecoration( + color: colorScheme.surfaceContainer, + border: Border.all( + color: colorScheme.outlineVariant, + width: 1, + ), + borderRadius: BorderRadius.circular(AppRadius.xl), + ), + child: Column( + children: [ + _CoinRow( + symbol: 'BTC', + pair: 'BTC/USDT', + fullName: 'Bitcoin', + price: '68,432.50', + change: '+2.35%', + isUp: true, + colorScheme: colorScheme, + isDark: isDark, + ), + Divider( + height: 1, + thickness: 1, + color: colorScheme.outlineVariant.withValues(alpha: 0.15), + ), + _CoinRow( + symbol: 'ETH', + pair: 'ETH/USDT', + fullName: 'Ethereum', + price: '3,856.20', + change: '+1.82%', + isUp: true, + colorScheme: colorScheme, + isDark: isDark, + ), + Divider( + height: 1, + thickness: 1, + color: colorScheme.outlineVariant.withValues(alpha: 0.15), + ), + _CoinRow( + symbol: 'SOL', + pair: 'SOL/USDT', + fullName: 'Solana', + price: '178.65', + change: '-0.94%', + isUp: false, + colorScheme: colorScheme, + isDark: isDark, + ), + ], + ), + ), + ], + ); + } +} + +class _CoinRow extends StatelessWidget { + const _CoinRow({ + required this.symbol, + required this.pair, + required this.fullName, + required this.price, + required this.change, + required this.isUp, + required this.colorScheme, + required this.isDark, + }); + + final String symbol; + final String pair; + final String fullName; + final String price; + final String change; + final bool isUp; + final ColorScheme colorScheme; + final bool isDark; + + @override + Widget build(BuildContext context) { + final changeColor = isUp + ? AppColorScheme.getUpColor(isDark) + : AppColorScheme.down; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Left: avatar + name + Row( + children: [ + CircleAvatar( + radius: 18, + backgroundColor: colorScheme.primary.withValues(alpha: 0.1), + child: Text( + symbol, + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w700, + color: colorScheme.primary, + ), + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + pair, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + Text( + fullName, + style: GoogleFonts.inter( + fontSize: 11, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ], + ), + // Right: price + change + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + price, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + Text( + change, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + color: changeColor, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/flutter_monisuo/lib/ui/pages/home/quick_actions_row.dart b/flutter_monisuo/lib/ui/pages/home/quick_actions_row.dart new file mode 100644 index 0000000..58d9beb --- /dev/null +++ b/flutter_monisuo/lib/ui/pages/home/quick_actions_row.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import '../../../core/theme/app_spacing.dart'; + +/// 首页快捷操作栏 - 充值/提现/划转/盈亏/账单 +class QuickActionsRow extends StatelessWidget { + const QuickActionsRow({ + super.key, + this.onDeposit, + this.onWithdraw, + this.onTransfer, + this.onProfit, + this.onBills, + }); + + final VoidCallback? onDeposit; + final VoidCallback? onWithdraw; + final VoidCallback? onTransfer; + final VoidCallback? onProfit; + final VoidCallback? onBills; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + decoration: BoxDecoration( + color: colorScheme.surfaceContainer, + border: Border.all( + color: colorScheme.outlineVariant, + width: 1, + ), + borderRadius: BorderRadius.circular(AppRadius.xl), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _ActionItem( + icon: LucideIcons.arrowUpRight, + label: '充值', + colorScheme: colorScheme, + onTap: onDeposit, + ), + _ActionItem( + icon: LucideIcons.arrowDownLeft, + label: '提现', + colorScheme: colorScheme, + onTap: onWithdraw, + ), + _ActionItem( + icon: LucideIcons.repeat, + label: '划转', + colorScheme: colorScheme, + onTap: onTransfer, + ), + _ActionItem( + icon: LucideIcons.chartPie, + label: '盈亏', + colorScheme: colorScheme, + onTap: onProfit, + ), + _ActionItem( + icon: LucideIcons.fileText, + label: '账单', + colorScheme: colorScheme, + onTap: onBills, + ), + ], + ), + ); + } +} + +class _ActionItem extends StatelessWidget { + const _ActionItem({ + required this.icon, + required this.label, + required this.colorScheme, + required this.onTap, + }); + + final IconData icon; + final String label; + final ColorScheme colorScheme; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(10), + ), + alignment: Alignment.center, + child: Icon( + icon, + size: 18, + color: colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: 6), + Text( + label, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ); + } +} diff --git a/flutter_monisuo/lib/ui/pages/market/market_page.dart b/flutter_monisuo/lib/ui/pages/market/market_page.dart index b72e854..8b4b5bf 100644 --- a/flutter_monisuo/lib/ui/pages/market/market_page.dart +++ b/flutter_monisuo/lib/ui/pages/market/market_page.dart @@ -1,9 +1,11 @@ +import 'dart:math'; 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 '../../../core/theme/app_spacing.dart' show AppRadius; import '../../../data/models/coin.dart'; import '../../../providers/market_provider.dart'; import '../../components/glass_panel.dart'; @@ -53,24 +55,30 @@ class _MarketPageState extends State backgroundColor: colorScheme.surfaceContainerHighest, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - padding: AppSpacing.pagePadding, + padding: const EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 上半区:BTC + ETH 突出展示 - _buildFeaturedSection(provider), - SizedBox(height: AppSpacing.lg), - // 下半区标题 - Text( - '代币列表', - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, + // 页面标题 "行情" + Padding( + padding: const EdgeInsets.only(top: 0, bottom: 8), + child: Text( + '行情', + style: GoogleFonts.inter( + fontSize: 22, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, + ), ), ), - SizedBox(height: AppSpacing.md), - // 下半区:代币列表 + const SizedBox(height: AppSpacing.md), + // 精选区域:BTC + ETH 卡片 + _buildFeaturedSection(provider), + const SizedBox(height: AppSpacing.md), + // 分区标题:全部币种 + 更多 + _buildSectionHeader(), + const SizedBox(height: AppSpacing.md), + // 币种列表卡片 _buildCoinList(provider), ], ), @@ -81,7 +89,7 @@ class _MarketPageState extends State ); } - /// 上半区:BTC + ETH 大卡片 + /// 精选区域:BTC + ETH 大卡片 Widget _buildFeaturedSection(MarketProvider provider) { final featured = provider.featuredCoins; if (featured.isEmpty) return const SizedBox.shrink(); @@ -95,7 +103,7 @@ class _MarketPageState extends State Expanded(child: _FeaturedCard(coin: btc)) else const Expanded(child: SizedBox.shrink()), - SizedBox(width: AppSpacing.md), + const SizedBox(width: 12), if (eth != null) Expanded(child: _FeaturedCard(coin: eth)) else @@ -104,9 +112,37 @@ class _MarketPageState extends State ); } - /// 下半区:代币列表 + /// 分区标题:全部币种 + 更多 + Widget _buildSectionHeader() { + final colorScheme = Theme.of(context).colorScheme; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '全部币种', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + Text( + '更多 >', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ); + } + + /// 币种列表 Widget _buildCoinList(MarketProvider provider) { final coins = provider.otherCoins; + final colorScheme = Theme.of(context).colorScheme; if (coins.isEmpty) { return _EmptyState( @@ -116,12 +152,28 @@ class _MarketPageState extends State ); } - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: coins.length, - separatorBuilder: (_, __) => SizedBox(height: AppSpacing.sm), - itemBuilder: (context, index) => _CoinListItem(coin: coins[index]), + return Container( + decoration: BoxDecoration( + color: colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + width: 1, + ), + ), + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: coins.length, + separatorBuilder: (_, __) => Divider( + height: 1, + thickness: 1, + color: colorScheme.outlineVariant.withOpacity(0.5 * 0.15), + indent: 16, + endIndent: 16, + ), + itemBuilder: (context, index) => _CoinRow(coin: coins[index]), + ), ); } @@ -135,13 +187,13 @@ class _MarketPageState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.circleAlert, size: 48, color: colorScheme.error), - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), Text( provider.error ?? '加载失败', style: TextStyle(color: colorScheme.error), textAlign: TextAlign.center, ), - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), ShadButton( onPressed: () => provider.refresh(), child: const Text('重试'), @@ -153,7 +205,7 @@ class _MarketPageState extends State } } -/// 上半区大卡片:BTC / ETH +/// 精选卡片:BTC / ETH (130px 高度,含迷你柱状图) class _FeaturedCard extends StatelessWidget { final Coin coin; @@ -168,89 +220,146 @@ class _FeaturedCard extends StatelessWidget { isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down; final changeBgColor = isUp ? AppColorScheme.getUpBackgroundColor(isDark) - : colorScheme.error.withOpacity(0.1); + : AppColorScheme.getDownBackgroundColor(isDark); return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 图标 + 币种代码 - Row( - children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.xl), - ), - child: Center( - child: Text( - coin.displayIcon, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: colorScheme.primary, - ), - ), - ), - ), - SizedBox(width: AppSpacing.sm), - Expanded( - child: Text( - coin.code, - style: GoogleFonts.spaceGrotesk( - fontSize: 18, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - ), - ], - ), - SizedBox(height: AppSpacing.md), - // 当前价格 - Text( - '\$${coin.formattedPrice}', - style: GoogleFonts.spaceGrotesk( - fontSize: 18, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - SizedBox(height: AppSpacing.xs), - // 24h 涨跌幅 - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.sm, - vertical: AppSpacing.xs, - ), - decoration: BoxDecoration( - color: changeBgColor, - borderRadius: BorderRadius.circular(AppRadius.sm), - border: Border.all(color: changeColor.withOpacity(0.2)), - ), - child: Text( - coin.formattedChange, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w700, - color: changeColor, + padding: const EdgeInsets.all(16), + height: 130, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 第一行:币种名称 + 涨跌徽章 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${coin.code}/USDT', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, ), ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: changeBgColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + coin.formattedChange, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w600, + color: changeColor, + ), + ), + ), + ], + ), + // 第二行:价格 + Text( + '\$${_formatFeaturedPrice(coin)}', + style: GoogleFonts.inter( + fontSize: 24, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, ), - ], - ), + ), + // 第三行:币种全名 + Text( + coin.name, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + ), + ), + // 第四行:迷你柱状图 + Expanded( + child: _MiniBarChart(isUp: isUp, isDark: isDark, seed: coin.code.hashCode), + ), + ], + ), ); } + + /// 精选卡片使用简短价格格式(带逗号) + String _formatFeaturedPrice(Coin coin) { + if (coin.price >= 1000) { + return _addCommas(coin.price.toStringAsFixed(2)); + } + return coin.price.toStringAsFixed(2); + } + + String _addCommas(String text) { + final parts = text.split('.'); + final intPart = parts[0]; + final decPart = parts.length > 1 ? '.${parts[1]}' : ''; + final buffer = StringBuffer(); + int count = 0; + for (int i = intPart.length - 1; i >= 0; i--) { + if (count > 0 && count % 3 == 0) { + buffer.write(','); + } + buffer.write(intPart[i]); + count++; + } + return '${buffer.toString().split('').reversed.join()}$decPart'; + } } -/// 下半区列表项 -class _CoinListItem extends StatelessWidget { +/// 迷你柱状图(模拟价格走势) +class _MiniBarChart extends StatelessWidget { + final bool isUp; + final bool isDark; + final int seed; + + const _MiniBarChart({required this.isUp, required this.isDark, required this.seed}); + + @override + Widget build(BuildContext context) { + final barColor = isUp + ? AppColorScheme.getUpColor(isDark) + : AppColorScheme.getDownColor(isDark); + + // 生成随机但确定的高度序列 + final heights = _generateHeights(); + + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: heights.map((h) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 1.5), + child: Container( + height: h, + decoration: BoxDecoration( + color: barColor, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + ); + }).toList(), + ); + } + + List _generateHeights() { + final random = Random(seed); + final base = 8.0; + final range = 16.0; + return List.generate(6, (_) => base + random.nextDouble() * range); + } +} + +/// 币种列表行 +class _CoinRow extends StatelessWidget { final Coin coin; - const _CoinListItem({required this.coin}); + const _CoinRow({required this.coin}); @override Widget build(BuildContext context) { @@ -258,102 +367,72 @@ class _CoinListItem extends StatelessWidget { final isDark = Theme.of(context).brightness == Brightness.dark; final isUp = coin.isUp; final changeColor = - isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down; + isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.getDownColor(isDark); final changeBgColor = isUp ? AppColorScheme.getUpBackgroundColor(isDark) - : colorScheme.error.withOpacity(0.1); + : AppColorScheme.getDownBackgroundColor(isDark); return GestureDetector( onTap: () => _navigateToTrade(context), - child: GlassPanel( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, - vertical: AppSpacing.sm + AppSpacing.xs, - ), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ - // 币种图标 - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.xl), - ), - child: Center( - child: Text( - coin.displayIcon, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: colorScheme.primary, - ), - ), - ), - ), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), - // 币种信息 + // 头像:圆形字母头像 + _CoinAvatar(letter: coin.displayIcon, code: coin.code), + const SizedBox(width: 10), + // 币种信息:交易对 + 全名 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [ - Text( - coin.code, - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - SizedBox(width: AppSpacing.xs), - Text( - '/USDT', - style: TextStyle( - fontSize: 11, - color: colorScheme.onSurfaceVariant, - ), - ), - ], + Text( + '${coin.code}/USDT', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, + ), ), + const SizedBox(height: 2), Text( coin.name, - style: TextStyle( - fontSize: 12, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, color: colorScheme.onSurfaceVariant, ), ), ], ), ), - // 价格和涨跌幅 + // 右侧:价格 + 涨跌标签 Column( crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, children: [ Text( '\$${coin.formattedPrice}', - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), + const SizedBox(height: 2), Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.sm, - vertical: 2, - ), + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: changeBgColor, - borderRadius: BorderRadius.circular(AppRadius.sm), - border: Border.all(color: changeColor.withOpacity(0.2)), + borderRadius: BorderRadius.circular(4), ), child: Text( coin.formattedChange, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w700, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w500, color: changeColor, ), ), @@ -372,6 +451,55 @@ class _CoinListItem extends StatelessWidget { } } +/// 币种头像组件 +class _CoinAvatar extends StatelessWidget { + final String letter; + final String code; + + const _CoinAvatar({required this.letter, required this.code}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + // 从 .pen 设计中的 accent-light 和 accent-primary + final bgColor = colorScheme.primary.withOpacity(isDark ? 0.15 : 0.1); + final textColor = colorScheme.primary; + + return Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: bgColor, + shape: BoxShape.circle, + ), + child: Center( + child: Text( + _getLetter(), + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w700, + color: textColor, + ), + ), + ), + ); + } + + String _getLetter() { + const letterMap = { + 'SOL': 'S', + 'BNB': 'B', + 'XRP': 'X', + 'DOGE': 'D', + 'ADA': 'A', + 'DOT': 'D', + }; + return letterMap[code] ?? code.substring(0, 1); + } +} + /// 空状态 class _EmptyState extends StatelessWidget { final IconData icon; @@ -386,17 +514,17 @@ class _EmptyState extends StatelessWidget { return Center( child: Padding( - padding: EdgeInsets.all(AppSpacing.xl), + padding: const EdgeInsets.all(AppSpacing.xl), child: Column( children: [ Icon(icon, size: 48, color: colorScheme.onSurfaceVariant), - SizedBox(height: AppSpacing.sm + AppSpacing.xs), + const SizedBox(height: 12), Text( message, style: TextStyle(color: colorScheme.onSurfaceVariant), ), if (onRetry != null) ...[ - SizedBox(height: AppSpacing.md), + const SizedBox(height: AppSpacing.md), ShadButton( onPressed: onRetry, child: const Text('重试'), diff --git a/flutter_monisuo/lib/ui/pages/mine/mine_page.dart b/flutter_monisuo/lib/ui/pages/mine/mine_page.dart index daf4f4c..e39bee2 100644 --- a/flutter_monisuo/lib/ui/pages/mine/mine_page.dart +++ b/flutter_monisuo/lib/ui/pages/mine/mine_page.dart @@ -2,34 +2,16 @@ 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 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../providers/auth_provider.dart'; import 'kyc_page.dart'; import '../../../providers/theme_provider.dart'; import '../auth/login_page.dart'; -import '../../components/glass_panel.dart'; -import '../../components/neon_glow.dart'; import 'welfare_center_page.dart'; -/// 菜单项数据模型 -class _MenuItem { - final IconData icon; - final String title; - final String? subtitle; - final Color? iconColor; - final VoidCallback onTap; - - const _MenuItem({ - required this.icon, - required this.title, - this.subtitle, - this.iconColor, - required this.onTap, - }); -} - -/// 我的页面 - Material Design 3 风格 +/// 我的页面 - 匹配 .pen 设计稿 class MinePage extends StatefulWidget { const MinePage({super.key}); @@ -37,7 +19,8 @@ class MinePage extends StatefulWidget { State createState() => _MinePageState(); } -class _MinePageState extends State with AutomaticKeepAliveClientMixin { +class _MinePageState extends State + with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; @@ -51,26 +34,31 @@ class _MinePageState extends State with AutomaticKeepAliveClientMixin body: Consumer( builder: (context, auth, _) { return SingleChildScrollView( - padding: AppSpacing.pagePadding, + padding: EdgeInsets.fromLTRB( + AppSpacing.md, + AppSpacing.md, + AppSpacing.md, + AppSpacing.xl + AppSpacing.md, + ), child: Column( children: [ - _UserCard(user: auth.user), - SizedBox(height: AppSpacing.md), - _MenuList( - onShowComingSoon: _showComingSoon, - onShowAbout: _showAboutDialog, + _ProfileCard(user: auth.user), + SizedBox(height: AppSpacing.sm), + _MenuGroup1( kycStatus: auth.user?.kycStatus ?? 0, + onShowComingSoon: _showComingSoon, ), - SizedBox(height: AppSpacing.xl), - _LogoutButton(onLogout: () => _handleLogout(auth)), + SizedBox(height: AppSpacing.sm), + _MenuGroup2(onShowAbout: _showAboutDialog), SizedBox(height: AppSpacing.lg), - // 版本信息 + _LogoutButton(onLogout: () => _handleLogout(auth)), + SizedBox(height: AppSpacing.md), Text( - 'System Build v1.0.0-Neo', - style: TextStyle( - fontSize: 10, - color: colorScheme.onSurfaceVariant.withOpacity(0.4), - letterSpacing: 0.3, + 'System Build v1.0.0', + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant.withOpacity(0.5), ), ), ], @@ -110,7 +98,7 @@ class _MinePageState extends State with AutomaticKeepAliveClientMixin builder: (context) => ShadDialog( title: Row( children: [ - _AppLogo(radius: 20, fontSize: 16), + _AvatarCircle(radius: 20, fontSize: 16), SizedBox(width: AppSpacing.sm + AppSpacing.xs), const Text('模拟所'), ], @@ -126,7 +114,9 @@ class _MinePageState extends State with AutomaticKeepAliveClientMixin SizedBox(height: AppSpacing.md), _InfoRow(icon: Icons.code, text: '版本: 1.0.0'), SizedBox(height: AppSpacing.sm), - _InfoRow(icon: Icons.favorite, text: 'Built with Flutter & Material Design 3'), + _InfoRow( + icon: Icons.favorite, + text: 'Built with Flutter & Material Design 3'), ], ), actions: [ @@ -169,101 +159,70 @@ class _MinePageState extends State with AutomaticKeepAliveClientMixin } } -/// 用户卡片组件 - Material Design 3 风格 -class _UserCard extends StatelessWidget { - final dynamic user; +// ============================================================ +// Profile Card +// ============================================================ - const _UserCard({required this.user}); +/// 用户资料卡片 - 头像 + 用户名 + 徽章 + chevron +class _ProfileCard extends StatelessWidget { + final dynamic user; + const _ProfileCard({required this.user}); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.sm), + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), child: Row( children: [ - // 头像 - 带霓虹边框 - Stack( - children: [ - Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: colorScheme.primary.withOpacity(isDark ? 0.15 : 0.08), - blurRadius: 20, - ), - ], - ), - child: _AppLogo(radius: 36, fontSize: 20, text: user?.avatarText), - ), - // 验证徽章 - Positioned( - bottom: 0, - right: 0, - child: Container( - padding: EdgeInsets.all(4), - decoration: BoxDecoration( - color: AppColorScheme.up, - shape: BoxShape.circle, - border: Border.all( - color: colorScheme.background, - width: 2, - ), - ), - child: Icon( - Icons.verified, - size: 14, - color: colorScheme.onTertiary, - ), - ), - ), - ], + // Avatar + _AvatarCircle( + radius: 24, + fontSize: 18, + text: user?.avatarText, ), - SizedBox(width: AppSpacing.md + AppSpacing.xs), + const SizedBox(width: 12), + // Name + badge column Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user?.username ?? '未登录', - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), - SizedBox(height: AppSpacing.sm), - // 用户等级标签 - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, - vertical: AppSpacing.xs, - ), - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.full), - border: Border.all( - color: colorScheme.primary.withOpacity(0.2), - ), - ), - child: Text( - '普通用户', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w700, - letterSpacing: 0.2, - color: colorScheme.primary, - ), + const SizedBox(height: 4), + Text( + '普通用户', + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, ), ), ], ), ), + // Chevron Icon( LucideIcons.chevronRight, + size: 16, color: colorScheme.onSurfaceVariant, ), ], @@ -272,34 +231,486 @@ class _UserCard extends StatelessWidget { } } -/// 应用 Logo 组件 -class _AppLogo extends StatelessWidget { +/// 圆形头像组件 +class _AvatarCircle extends StatelessWidget { final double radius; final double fontSize; final String? text; - const _AppLogo({required this.radius, required this.fontSize, this.text}); + const _AvatarCircle({ + required this.radius, + required this.fontSize, + this.text, + }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; return CircleAvatar( radius: radius, - backgroundColor: colorScheme.primary.withOpacity(0.2), + backgroundColor: colorScheme.primary.withOpacity(0.15), child: Text( text ?? '₿', style: TextStyle( fontSize: fontSize, color: colorScheme.primary, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w700, ), ), ); } } -/// 信息行组件 +// ============================================================ +// Menu Group 1 - 福利中心 / 实名认证 / 安全设置 / 消息通知 +// ============================================================ + +class _MenuGroup1 extends StatelessWidget { + final int kycStatus; + final void Function(String) onShowComingSoon; + + const _MenuGroup1({ + required this.kycStatus, + required this.onShowComingSoon, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Container( + width: double.infinity, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), + child: Column( + children: [ + // 福利中心 + _MenuRow( + icon: LucideIcons.gift, + iconColor: AppColorScheme.darkSecondary, // gold + title: '福利中心', + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const WelfareCenterPage()), + ); + }, + ), + _MenuDivider(), + // 实名认证 + _MenuRow( + icon: LucideIcons.shieldCheck, + iconColor: AppColorScheme.getUpColor(isDark), + title: '实名认证', + trailing: _KycBadge(kycStatus: kycStatus), + onTap: () { + if (kycStatus == 2) { + _showKycStatusDialog(context); + } else { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const KycPage()), + ); + } + }, + ), + _MenuDivider(), + // 安全设置 + _MenuRow( + icon: LucideIcons.lock, + iconColor: colorScheme.onSurfaceVariant, + title: '安全设置', + onTap: () => onShowComingSoon('安全设置'), + ), + _MenuDivider(), + // 消息通知 + _MenuRow( + icon: LucideIcons.bell, + iconColor: colorScheme.onSurfaceVariant, + title: '消息通知', + trailing: _RedDotIndicator(), + onTap: () => onShowComingSoon('消息通知'), + ), + ], + ), + ); + } +} + +// ============================================================ +// Menu Group 2 - 深色模式 / 系统设置 / 关于我们 +// ============================================================ + +class _MenuGroup2 extends StatelessWidget { + final VoidCallback onShowAbout; + + const _MenuGroup2({required this.onShowAbout}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Container( + width: double.infinity, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), + child: Column( + children: [ + // 深色模式 + _DarkModeRow(), + _MenuDivider(), + // 系统设置 + _MenuRow( + icon: LucideIcons.settings, + iconColor: colorScheme.onSurfaceVariant, + title: '系统设置', + onTap: () { + // TODO: 系统设置 + }, + ), + _MenuDivider(), + // 关于我们 + _MenuRow( + icon: LucideIcons.info, + iconColor: colorScheme.onSurfaceVariant, + title: '关于我们', + onTap: onShowAbout, + ), + ], + ), + ); + } +} + +// ============================================================ +// Shared menu row components +// ============================================================ + +/// 单行菜单项:icon-in-box + title + trailing (chevron / badge / toggle) +class _MenuRow extends StatelessWidget { + final IconData icon; + final Color iconColor; + final String title; + final Widget? trailing; + final VoidCallback? onTap; + + const _MenuRow({ + required this.icon, + required this.iconColor, + required this.title, + this.trailing, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + children: [ + // Icon in 36x36 rounded container + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? Theme.of(context).colorScheme.surfaceContainerHigh + : Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Icon(icon, size: 18, color: iconColor), + ), + ), + const SizedBox(width: 10), + // Title + Expanded( + child: Text( + title, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + // Trailing + if (trailing != null) + trailing! + else + Icon( + LucideIcons.chevronRight, + size: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ], + ), + ), + ); + } +} + +/// Menu group divider +class _MenuDivider extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + height: 1, + color: Theme.of(context).colorScheme.outlineVariant.withOpacity(0.15), + margin: const EdgeInsets.only(left: 62), + ); + } +} + +// ============================================================ +// Special trailing widgets +// ============================================================ + +/// KYC status badge (e.g. "已认证" green badge + chevron) +class _KycBadge extends StatelessWidget { + final int kycStatus; + const _KycBadge({required this.kycStatus}); + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final green = AppColorScheme.getUpColor(isDark); + + if (kycStatus == 2) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: green.withOpacity(0.1), + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Text( + '已认证', + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w500, + color: green, + ), + ), + ), + const SizedBox(width: 8), + Icon( + LucideIcons.chevronRight, + size: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ], + ); + } + + if (kycStatus == 1) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: AppColorScheme.warning.withOpacity(0.1), + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Text( + '审核中', + style: GoogleFonts.inter( + fontSize: 10, + fontWeight: FontWeight.w500, + color: AppColorScheme.warning, + ), + ), + ), + const SizedBox(width: 8), + Icon( + LucideIcons.chevronRight, + size: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ], + ); + } + + return Icon( + LucideIcons.chevronRight, + size: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ); + } +} + +/// Red dot indicator for notifications + chevron +class _RedDotIndicator extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: AppColorScheme.down, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Icon( + LucideIcons.chevronRight, + size: 16, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ], + ); + } +} + +/// Dark mode toggle row +class _DarkModeRow extends StatelessWidget { + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + final themeProvider = context.watch(); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + children: [ + // Icon in 36x36 rounded container + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainerHigh + : colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Icon( + LucideIcons.moon, + size: 18, + color: colorScheme.onSurfaceVariant, + ), + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + '深色模式', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, + ), + ), + ), + // Toggle switch - matching .pen design (44x24 rounded pill) + GestureDetector( + onTap: () => themeProvider.toggleTheme(), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 44, + height: 24, + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainerHigh + : colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: AnimatedAlign( + duration: const Duration(milliseconds: 200), + alignment: + themeProvider.isDarkMode + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: colorScheme.onSurface, + shape: BoxShape.circle, + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +// ============================================================ +// Logout button +// ============================================================ + +class _LogoutButton extends StatelessWidget { + final VoidCallback onLogout; + const _LogoutButton({required this.onLogout}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return GestureDetector( + onTap: onLogout, + child: Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + color: AppColorScheme.down.withOpacity(0.05), + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: AppColorScheme.down.withOpacity(0.15), + ), + ), + child: Center( + child: Text( + '退出登录', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColorScheme.down, + ), + ), + ), + ), + ); + } +} + +// ============================================================ +// Info row (used in about dialog) +// ============================================================ + class _InfoRow extends StatelessWidget { final IconData icon; final String text; @@ -326,118 +737,9 @@ class _InfoRow extends StatelessWidget { } } -/// 菜单列表组件 - Glass Panel 风格 -class _MenuList extends StatelessWidget { - final void Function(String) onShowComingSoon; - final VoidCallback onShowAbout; - final int kycStatus; - - const _MenuList({ - required this.onShowComingSoon, - required this.onShowAbout, - required this.kycStatus, - }); - - @override - Widget build(BuildContext context) { - final themeProvider = context.watch(); - final colorScheme = Theme.of(context).colorScheme; - - return GlassPanel( - padding: EdgeInsets.zero, - borderRadius: BorderRadius.circular(AppRadius.xxl), - child: Column( - children: [ - // 主题切换开关 - _ThemeToggleTile(isDarkMode: themeProvider.isDarkMode), - _buildDivider(), - // 菜单项 - ..._buildMenuItems(context, colorScheme), - ], - ), - ); - } - - Widget _buildDivider() { - return Container( - margin: EdgeInsets.only(left: 56), - height: 1, - color: AppColorScheme.glassPanelBorder, - ); - } - - List _buildMenuItems(BuildContext context, ColorScheme colorScheme) { - final items = [ - _MenuItem( - icon: LucideIcons.gift, - title: '福利中心', - subtitle: '首充奖励 + 推广奖励', - iconColor: colorScheme.primary, - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const WelfareCenterPage()), - ); - }, - ), - _MenuItem( - icon: LucideIcons.userCheck, - title: '实名认证', - subtitle: kycStatus == 2 - ? '已认证' - : kycStatus == 1 - ? '审核中' - : '完成实名认证,解锁更多功能', - iconColor: kycStatus == 2 ? AppColorScheme.up : colorScheme.primary, - onTap: () { - if (kycStatus == 2) { - _showKycStatusDialog(context); - } else { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const KycPage()), - ); - } - }, - ), - _MenuItem( - icon: LucideIcons.shield, - title: '安全设置', - subtitle: '密码、二次验证等安全设置', - iconColor: colorScheme.secondary, - onTap: () => onShowComingSoon('安全设置'), - ), - _MenuItem( - icon: LucideIcons.bell, - title: '消息通知', - subtitle: '管理消息推送设置', - iconColor: AppColorScheme.up, - onTap: () => onShowComingSoon('消息通知'), - ), - _MenuItem( - icon: LucideIcons.settings, - title: '系统设置', - subtitle: '主题、语言等偏好设置', - iconColor: colorScheme.primary, - onTap: () => onShowComingSoon('系统设置'), - ), - _MenuItem( - icon: LucideIcons.info, - title: '关于我们', - subtitle: '版本信息与用户协议', - iconColor: colorScheme.onSurfaceVariant, - onTap: onShowAbout, - ), - ]; - - return [ - for (var i = 0; i < items.length; i++) ...[ - _MenuItemTile(item: items[i]), - if (i < items.length - 1) _buildDivider(), - ], - ]; - } -} +// ============================================================ +// KYC status dialog +// ============================================================ void _showKycStatusDialog(BuildContext context) { showShadDialog( @@ -460,168 +762,3 @@ void _showKycStatusDialog(BuildContext context) { ), ); } - -/// 主题切换组件 -class _ThemeToggleTile extends StatelessWidget { - final bool isDarkMode; - - const _ThemeToggleTile({required this.isDarkMode}); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final themeProvider = context.read(); - - return InkWell( - onTap: () => themeProvider.toggleTheme(), - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, - vertical: AppSpacing.sm + AppSpacing.xs, - ), - child: Row( - children: [ - _MenuIcon( - icon: isDarkMode ? LucideIcons.moon : LucideIcons.sun, - color: colorScheme.primary, - ), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '深色模式', - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w500, - color: colorScheme.onSurface, - ), - ), - SizedBox(height: AppSpacing.xs / 2), - Text( - isDarkMode ? '当前:深色主题' : '当前:浅色主题', - style: TextStyle( - fontSize: 11, - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - Switch( - value: isDarkMode, - onChanged: (_) => themeProvider.toggleTheme(), - activeTrackColor: colorScheme.primary.withOpacity(0.5), - activeColor: colorScheme.primary, - ), - ], - ), - ), - ); - } -} - -/// 菜单项组件 -class _MenuItemTile extends StatelessWidget { - final _MenuItem item; - - const _MenuItemTile({required this.item}); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - return InkWell( - onTap: item.onTap, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, - vertical: AppSpacing.sm + AppSpacing.xs, - ), - child: Row( - children: [ - _MenuIcon(icon: item.icon, color: item.iconColor), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.title, - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w500, - color: colorScheme.onSurface, - ), - ), - if (item.subtitle != null) ...[ - SizedBox(height: AppSpacing.xs / 2), - Text( - item.subtitle!, - style: TextStyle( - fontSize: 11, - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ], - ), - ), - Icon( - LucideIcons.chevronRight, - size: 18, - color: colorScheme.onSurfaceVariant, - ), - ], - ), - ), - ); - } -} - -/// 菜单图标组件 - Material Design 3 风格 -class _MenuIcon extends StatelessWidget { - final IconData icon; - final Color? color; - - const _MenuIcon({required this.icon, this.color}); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final iconColor = color ?? colorScheme.primary; - - return Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: iconColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.md + AppSpacing.xs), - border: Border.all( - color: iconColor.withOpacity(0.2), - ), - ), - child: Icon(icon, size: 20, color: iconColor), - ); - } -} - -/// 退出登录按钮 - 带霓虹光效 -class _LogoutButton extends StatelessWidget { - final VoidCallback onLogout; - - const _LogoutButton({required this.onLogout}); - - @override - Widget build(BuildContext context) { - return NeonButton( - text: 'Logout Terminal', - type: NeonButtonType.error, - icon: Icons.logout, - onPressed: onLogout, - width: double.infinity, - showGlow: true, - ); - } -} diff --git a/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart b/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart index 4187529..d12b8b8 100644 --- a/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart +++ b/flutter_monisuo/lib/ui/pages/mine/welfare_center_page.dart @@ -10,8 +10,6 @@ import '../../../core/event/app_event_bus.dart'; import '../../../data/services/bonus_service.dart'; import '../../../providers/asset_provider.dart'; import '../../../providers/auth_provider.dart'; -import '../../components/glass_panel.dart'; -import '../../components/neon_glow.dart'; /// 福利中心页面 class WelfareCenterPage extends StatefulWidget { @@ -46,54 +44,117 @@ class _WelfareCenterPageState extends State { } } + // ============================================ + // 主题感知颜色辅助 + // ============================================ + + /// 金色强调色 ($gold-accent) + Color _goldAccent(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? AppColorScheme.darkSecondary : const Color(0xFFF59E0B); + } + + /// 金色强调色带透明度 + Color _goldAccentWithOpacity(BuildContext context, double opacity) { + return _goldAccent(context).withOpacity(opacity); + } + + /// 盈利绿色 ($profit-green) + Color _profitGreen(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? const Color(0xFF4ADE80) : const Color(0xFF16A34A); + } + + /// 盈利绿色背景 ($profit-green-bg) + Color _profitGreenBg(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? const Color(0xFF052E16) : const Color(0xFFF0FDF4); + } + + /// 文字静默色 ($text-muted) + Color _textMuted(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? const Color(0xFF64748B) : const Color(0xFF94A3B8); + } + + /// 第三级背景色 ($bg-tertiary) + Color _bgTertiary(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? AppColorScheme.darkSurfaceContainerHigh : const Color(0xFFF1F5F9); + } + + /// 卡片表面色 ($surface-card) + Color _surfaceCard(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? AppColorScheme.darkSurfaceContainer : Colors.white; + } + + /// 反色文字 ($text-inverse) + Color _textInverse(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? const Color(0xFF0F172A) : Colors.white; + } + @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( - backgroundColor: colorScheme.background, + backgroundColor: isDark + ? AppColorScheme.darkBackground + : const Color(0xFFF8FAFC), appBar: AppBar( - backgroundColor: Colors.transparent, + backgroundColor: isDark + ? AppColorScheme.darkBackground + : Colors.white, elevation: 0, + scrolledUnderElevation: 0, + centerTitle: false, + titleSpacing: 0, title: Text( '福利中心', - style: GoogleFonts.spaceGrotesk( - fontWeight: FontWeight.bold, + style: GoogleFonts.inter( + fontSize: 17, + fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), leading: IconButton( - icon: Icon(LucideIcons.chevronLeft, color: colorScheme.onSurface), + icon: Icon(LucideIcons.arrowLeft, color: colorScheme.onSurface, size: 24), onPressed: () => Navigator.of(context).pop(), ), ), body: _isLoading ? Center( - child: CircularProgressIndicator(color: colorScheme.primary), + child: CircularProgressIndicator( + color: _goldAccent(context), + strokeWidth: 2.5, + ), ) : RefreshIndicator( onRefresh: _loadData, - color: colorScheme.primary, + color: _goldAccent(context), child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - padding: AppSpacing.pagePadding, + padding: const EdgeInsets.fromLTRB(16, 8, 16, 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 推广码卡片 - _buildReferralCodeCard(colorScheme), - SizedBox(height: AppSpacing.lg), + // 推广码卡片(金色渐变边框) + _buildReferralCodeCard(context), + const SizedBox(height: 16), - // 首充福利卡片 - _buildNewUserBonusCard(colorScheme), - SizedBox(height: AppSpacing.lg), + // 新人福利卡片 + _buildNewUserBonusCard(context), + const SizedBox(height: 16), // 推广奖励列表 - _buildReferralRewardsSection(colorScheme), - SizedBox(height: AppSpacing.lg), + _buildReferralRewardsSection(context), + const SizedBox(height: 16), - // 规则说明 - _buildRulesCard(colorScheme), + // 奖励规则 + _buildRulesCard(context), ], ), ), @@ -101,98 +162,92 @@ class _WelfareCenterPageState extends State { ); } - /// 推广码卡片 - Widget _buildReferralCodeCard(ColorScheme colorScheme) { - final referralCode = _welfareData?['referralCode'] as String? ?? ''; + // ============================================ + // 推广码卡片(金色渐变边框 Hero Card) + // ============================================ - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg), + Widget _buildReferralCodeCard(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final referralCode = _welfareData?['referralCode'] as String? ?? ''; + final gold = _goldAccent(context); + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(AppRadius.xl), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + _goldAccentWithOpacity(context, 0.15), + _surfaceCard(context), + ], + ), + border: Border.all( + color: _goldAccentWithOpacity(context, 0.3), + width: 1, + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Header Row: gift icon + 标题 Row( children: [ - Container( - padding: EdgeInsets.all(AppSpacing.sm), - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.md), - ), - child: Icon( - LucideIcons.users, - color: colorScheme.primary, - size: 20, - ), - ), - SizedBox(width: AppSpacing.md), + Icon(LucideIcons.gift, color: gold, size: 24), + const SizedBox(width: 10), Text( - '我的推广码', - style: GoogleFonts.spaceGrotesk( + '我的邀请码', + style: GoogleFonts.inter( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), ], ), - SizedBox(height: AppSpacing.lg), - Container( - width: double.infinity, - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.lg, - vertical: AppSpacing.md, - ), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(AppRadius.lg), - border: Border.all( - color: colorScheme.outlineVariant.withOpacity(0.2), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - referralCode.isEmpty ? '暂无推广码' : referralCode, - style: GoogleFonts.spaceGrotesk( - fontSize: 24, - fontWeight: FontWeight.bold, - letterSpacing: 4, - color: referralCode.isEmpty - ? colorScheme.onSurfaceVariant - : colorScheme.primary, - ), - ), - if (referralCode.isNotEmpty) ...[ - SizedBox(width: AppSpacing.md), - GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: referralCode)); - ToastUtils.show('推广码已复制'); - }, - child: Container( - padding: EdgeInsets.all(AppSpacing.xs + 2), - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - child: Icon( - LucideIcons.copy, - size: 18, - color: colorScheme.primary, - ), - ), - ), - ], - ], + const SizedBox(height: 16), + + // 邀请码 + Text( + referralCode.isEmpty ? '暂无邀请码' : referralCode, + style: GoogleFonts.inter( + fontSize: 24, + fontWeight: FontWeight.w800, + letterSpacing: 2, + color: gold, ), ), - SizedBox(height: AppSpacing.sm), - Text( - '分享推广码给好友,好友注册并充值满1000u后您可领取100u奖励', - style: TextStyle( - fontSize: 11, - color: colorScheme.onSurfaceVariant, + const SizedBox(height: 16), + + // 复制邀请码按钮 + SizedBox( + width: double.infinity, + height: 40, + child: ElevatedButton( + onPressed: referralCode.isEmpty + ? null + : () { + Clipboard.setData(ClipboardData(text: referralCode)); + ToastUtils.show('邀请码已复制'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: gold, + foregroundColor: _textInverse(context), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadius.lg), + ), + disabledBackgroundColor: gold.withOpacity(0.4), + ), + child: Text( + '复制邀请码', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), ), ), ], @@ -200,300 +255,217 @@ class _WelfareCenterPageState extends State { ); } - /// 首充福利卡片 - Widget _buildNewUserBonusCard(ColorScheme colorScheme) { + // ============================================ + // 新人福利卡片 + // ============================================ + + Widget _buildNewUserBonusCard(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; final newUserBonus = _welfareData?['newUserBonus'] as Map?; final eligible = newUserBonus?['eligible'] as bool? ?? false; final claimed = newUserBonus?['claimed'] as bool? ?? false; final deposited = newUserBonus?['deposited'] as bool? ?? false; + final green = _profitGreen(context); + final greenBg = _profitGreenBg(context); - String statusText; - Color statusColor; + // 状态标签 + String badgeText; + bool showAvailableBadge; String buttonText; bool canClaim; + String description; if (claimed) { - statusText = '100 USDT 已领取'; - statusColor = AppColorScheme.up; + badgeText = '已领取'; + showAvailableBadge = false; buttonText = '已领取'; canClaim = false; + description = '新人福利已领取'; } else if (eligible) { - statusText = '符合条件,立即领取!'; - statusColor = colorScheme.primary; - buttonText = '领取 100u'; + badgeText = '可领取'; + showAvailableBadge = true; + buttonText = '立即领取'; canClaim = true; + description = '完成首次充值即可领取'; } else { - statusText = deposited ? '已完成充值' : '完成首次充值后可领取'; - statusColor = colorScheme.onSurfaceVariant; + badgeText = deposited ? '已充值' : '待解锁'; + showAvailableBadge = false; buttonText = '未解锁'; canClaim = false; + description = deposited ? '已充值,等待系统确认' : '完成首次充值即可领取'; } - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg), + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: _surfaceCard(context), + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Header: 标题 + 状态标签 Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Container( - padding: EdgeInsets.all(AppSpacing.sm), - decoration: BoxDecoration( - color: (claimed ? AppColorScheme.up : colorScheme.primary) - .withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.md), - ), - child: Icon( - claimed ? LucideIcons.check : LucideIcons.gift, - color: claimed ? AppColorScheme.up : colorScheme.primary, - size: 20, + Text( + '新人福利', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, ), ), - SizedBox(width: AppSpacing.md), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '首充福利', - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), + if (showAvailableBadge) + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: greenBg, + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Text( + badgeText, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w600, + color: green, ), - SizedBox(height: 2), - Text( - statusText, - style: TextStyle( - fontSize: 12, - color: statusColor, - ), - ), - ], + ), ), - ), - SizedBox( - child: NeonButton( - text: buttonText, - type: canClaim ? NeonButtonType.primary : NeonButtonType.outline, - onPressed: canClaim ? () => _claimNewUserBonus() : null, - height: 36, - showGlow: canClaim, - ), - ), ], ), - SizedBox(height: AppSpacing.md), + const SizedBox(height: 12), + + // 金额 Text( - '新用户首次充值完成后可领取 100 USDT', - style: TextStyle( - fontSize: 11, + '+100 USDT', + style: GoogleFonts.inter( + fontSize: 28, + fontWeight: FontWeight.w800, + color: claimed ? colorScheme.onSurfaceVariant : green, + ), + ), + const SizedBox(height: 8), + + // 描述 + Text( + description, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.normal, color: colorScheme.onSurfaceVariant, ), ), + const SizedBox(height: 16), + + // 领取按钮 + SizedBox( + width: double.infinity, + height: 44, + child: ElevatedButton( + onPressed: canClaim ? () => _claimNewUserBonus() : null, + style: ElevatedButton.styleFrom( + backgroundColor: green, + foregroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(AppRadius.lg), + ), + disabledBackgroundColor: green.withOpacity(0.3), + disabledForegroundColor: Colors.white70, + ), + child: Text( + buttonText, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ), + ), ], ), ); } - /// 推广奖励列表 - Widget _buildReferralRewardsSection(ColorScheme colorScheme) { + // ============================================ + // 推广奖励列表 + // ============================================ + + Widget _buildReferralRewardsSection(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; final referralRewards = _welfareData?['referralRewards'] as List? ?? []; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '推广奖励', - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - SizedBox(height: AppSpacing.sm), - Text( - '被推广人每累计充值满 1000u,您可领取 100u 奖励(每人最多8次)', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - SizedBox(height: AppSpacing.md), - if (referralRewards.isEmpty) - GlassPanel( - padding: EdgeInsets.all(AppSpacing.xl), - child: Center( - child: Column( - children: [ - Icon( - LucideIcons.users, - size: 36, - color: colorScheme.onSurfaceVariant.withOpacity(0.4), - ), - SizedBox(height: AppSpacing.sm), - Text( - '暂无推广用户', - style: TextStyle( - color: colorScheme.onSurfaceVariant, - fontSize: 13, - ), - ), - ], + // Section Header + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '推广奖励', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, ), ), - ) - else - ...referralRewards.map((item) { - final data = item as Map; - return _buildReferralRewardCard(data, colorScheme); - }), + const SizedBox(height: 4), + Text( + '每邀请一位好友充值达标,奖励100 USDT', + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, + color: _textMuted(context), + ), + ), + ], + ), + const SizedBox(height: 12), + + // 推广列表卡片 + Container( + width: double.infinity, + decoration: BoxDecoration( + color: _surfaceCard(context), + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), + child: referralRewards.isEmpty + ? _buildEmptyReferralList(context) + : _buildReferralListItems(context, referralRewards), + ), ], ); } - Widget _buildReferralRewardCard( - Map data, ColorScheme colorScheme) { - final username = data['username'] as String? ?? ''; - final totalDeposit = data['totalDeposit']?.toString() ?? '0'; - final claimableCount = data['claimableCount'] as int? ?? 0; - final milestones = data['milestones'] as List? ?? []; - + Widget _buildEmptyReferralList(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; return Padding( - padding: EdgeInsets.only(bottom: AppSpacing.md), - child: GlassPanel( - padding: EdgeInsets.all(AppSpacing.md), + padding: const EdgeInsets.all(32), + child: Center( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - CircleAvatar( - radius: 16, - backgroundColor: colorScheme.primary.withOpacity(0.1), - child: Text( - username.isNotEmpty ? username[0].toUpperCase() : '?', - style: TextStyle( - color: colorScheme.primary, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - SizedBox(width: AppSpacing.sm), - Text( - username, - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - '累计充值: $totalDeposit U', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - if (claimableCount > 0) - Text( - '$claimableCount 个可领取', - style: TextStyle( - fontSize: 11, - color: colorScheme.primary, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ], + Icon( + LucideIcons.users, + size: 36, + color: _textMuted(context).withOpacity(0.4), ), - SizedBox(height: AppSpacing.sm), - // 里程碑进度 - Wrap( - spacing: 6, - runSpacing: 6, - children: milestones.map((m) { - final milestone = m as Map; - final earned = milestone['earned'] as bool? ?? false; - final claimed = milestone['claimed'] as bool? ?? false; - final claimable = milestone['claimable'] as bool? ?? false; - final milestoneNum = milestone['milestone'] as int? ?? 0; - - Color bgColor; - Color textColor; - VoidCallback? onTap; - - if (claimed) { - bgColor = AppColorScheme.up.withOpacity(0.15); - textColor = AppColorScheme.up; - onTap = null; - } else if (claimable) { - bgColor = colorScheme.primary.withOpacity(0.15); - textColor = colorScheme.primary; - onTap = () => _claimReferralBonus( - data['userId'] as int, - milestoneNum, - ); - } else if (earned) { - bgColor = colorScheme.surfaceContainerHigh; - textColor = colorScheme.onSurfaceVariant; - onTap = null; - } else { - bgColor = colorScheme.surfaceContainerHighest - .withOpacity(0.5); - textColor = colorScheme.onSurfaceVariant.withOpacity(0.5); - onTap = null; - } - - return GestureDetector( - onTap: onTap, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 6, - ), - decoration: BoxDecoration( - color: bgColor, - borderRadius: BorderRadius.circular(AppRadius.md), - border: Border.all( - color: claimable - ? colorScheme.primary.withOpacity(0.3) - : Colors.transparent, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (claimed) - Icon(LucideIcons.check, size: 12, color: textColor) - else if (claimable) - Icon(LucideIcons.gift, size: 12, color: textColor), - if (claimed || claimable) SizedBox(width: 4), - Text( - '${milestoneNum}k', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: textColor, - ), - ), - ], - ), - ), - ); - }).toList(), + const SizedBox(height: 8), + Text( + '暂无推广用户', + style: GoogleFonts.inter( + fontSize: 13, + color: colorScheme.onSurfaceVariant, + ), ), ], ), @@ -501,74 +473,246 @@ class _WelfareCenterPageState extends State { ); } - /// 规则说明 - Widget _buildRulesCard(ColorScheme colorScheme) { + Widget _buildReferralListItems(BuildContext context, List referralRewards) { + final colorScheme = Theme.of(context).colorScheme; + final gold = _goldAccent(context); + final green = _profitGreen(context); + final greenBg = _profitGreenBg(context); + + return Column( + children: List.generate(referralRewards.length, (index) { + final data = referralRewards[index] as Map; + final username = data['username'] as String? ?? ''; + final totalDeposit = data['totalDeposit']?.toString() ?? '0'; + final claimableCount = data['claimableCount'] as int? ?? 0; + final milestones = data['milestones'] as List? ?? []; + final isLast = index == referralRewards.length - 1; + + // 判断状态 + bool hasClaimable = claimableCount > 0; + bool hasAnyMilestone = milestones.isNotEmpty; + bool allClaimed = milestones.isNotEmpty && + milestones.every((m) => (m as Map)['claimed'] == true); + + // 进度计算 + double progress = 0; + if (milestones.isNotEmpty) { + int earnedCount = milestones.where((m) { + final milestone = m as Map; + return milestone['earned'] as bool? ?? false; + }).length; + progress = earnedCount / milestones.length; + } else { + // 无里程碑数据时,根据充值金额估算 + final deposit = double.tryParse(totalDeposit) ?? 0; + progress = (deposit / 1000).clamp(0.0, 1.0); + } + + // 状态颜色和文字 + Color progressColor; + Color statusTextColor; + String statusText; + Widget? actionWidget; + + if (hasClaimable) { + // 可领取 (achieved, green) + progressColor = green; + statusTextColor = green; + statusText = ''; + actionWidget = GestureDetector( + onTap: () => _claimReferralBonus(data['userId'] as int, milestones.isNotEmpty ? (milestones.firstWhere( + (m) => (m as Map)['claimable'] == true, + orElse: () => milestones.first, + ) as Map)['milestone'] as int? ?? 1 : 1), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), + decoration: BoxDecoration( + color: greenBg, + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Text( + '领取', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: green, + ), + ), + ), + ); + } else if (progress > 0) { + // 进行中 (amber / gold) + progressColor = gold; + statusTextColor = const Color(0xFFD97706); + statusText = ''; + actionWidget = Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFFFEF3C7), + borderRadius: BorderRadius.circular(AppRadius.sm), + ), + child: Text( + '进行中', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: statusTextColor, + ), + ), + ); + } else { + // 待达标 (gray) + progressColor = _bgTertiary(context); + statusTextColor = _textMuted(context); + statusText = ''; + actionWidget = Text( + '待达标', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w500, + color: _textMuted(context), + ), + ); + } + + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + // Top Row: avatar + name + deposit + action + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + // Avatar + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: _bgTertiary(context), + shape: BoxShape.circle, + ), + child: Center( + child: Text( + username.isNotEmpty ? username[0].toUpperCase() : '?', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, + ), + ), + ), + ), + const SizedBox(width: 10), + Text( + username, + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: colorScheme.onSurface, + ), + ), + const SizedBox(width: 10), + Text( + '充值: \u00A5$totalDeposit', + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + if (actionWidget != null) actionWidget, + ], + ), + const SizedBox(height: 10), + + // Progress bar + ClipRRect( + borderRadius: BorderRadius.circular(3), + child: SizedBox( + height: 6, + child: LinearProgressIndicator( + value: progress, + backgroundColor: _bgTertiary(context), + valueColor: AlwaysStoppedAnimation(progressColor), + minHeight: 6, + ), + ), + ), + ], + ), + ), + if (!isLast) + Divider( + height: 1, + thickness: 1, + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ], + ); + }), + ); + } + + // ============================================ + // 奖励规则卡片 + // ============================================ + + Widget _buildRulesCard(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Container( - padding: EdgeInsets.all(AppSpacing.md), + width: double.infinity, + padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), decoration: BoxDecoration( - color: colorScheme.surfaceContainerHigh.withOpacity(0.3), + color: _bgTertiary(context), borderRadius: BorderRadius.circular(AppRadius.lg), - border: Border.all( - color: colorScheme.outlineVariant.withOpacity(0.1), - ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Icon(LucideIcons.info, size: 16, color: colorScheme.onSurfaceVariant), - SizedBox(width: AppSpacing.sm), - Text( - '规则说明', - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - ], + Text( + '奖励规则', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), ), - SizedBox(height: AppSpacing.sm), - _buildRuleItem('新用户首次充值完成后可领取 100 USDT 首充福利', colorScheme), - _buildRuleItem('被推广人累计充值每满 1000u,推广人可领 100u', colorScheme), - _buildRuleItem('每位被推广人最多可触发 8 次推广奖励', colorScheme), - _buildRuleItem('所有奖励将直接存入资金账户', colorScheme), + const SizedBox(height: 8), + _buildRuleItem('新用户注册完成实名认证奖励 100 USDT', context), + _buildRuleItem('邀请好友充值每达 1000 USDT,双方各获得 100 USDT', context), + _buildRuleItem('奖励直接发放至资金账户', context), ], ), ); } - Widget _buildRuleItem(String text, ColorScheme colorScheme) { + Widget _buildRuleItem(String text, BuildContext context) { return Padding( - padding: EdgeInsets.symmetric(vertical: 3), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only(top: 6), - width: 4, - height: 4, - decoration: BoxDecoration( - color: colorScheme.onSurfaceVariant, - shape: BoxShape.circle, - ), - ), - SizedBox(width: AppSpacing.sm), - Expanded( - child: Text( - text, - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - ), - ], + padding: const EdgeInsets.symmetric(vertical: 3), + child: Text( + '\u2022 $text', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: Theme.of(context).brightness == Brightness.dark + ? AppColorScheme.darkOnSurfaceVariant + : const Color(0xFF475569), + ), ), ); } + // ============================================ + // 业务逻辑 + // ============================================ + Future _claimNewUserBonus() async { try { final bonusService = context.read(); diff --git a/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart b/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart index b4f2ccb..6eeebf7 100644 --- a/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart +++ b/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart @@ -1,9 +1,13 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:shadcn_ui/shadcn_ui.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:provider/provider.dart'; +import '../../../core/theme/app_color_scheme.dart'; +import '../../../core/theme/app_spacing.dart'; +import '../../../core/theme/app_theme.dart' show AppRadius; import '../../../core/utils/toast_utils.dart'; import '../../../core/event/app_event_bus.dart'; import '../../../providers/asset_provider.dart'; @@ -21,10 +25,6 @@ class _FundOrdersPageState extends State { int _activeTab = 0; // 0=全部, 1=充值, 2=提现 StreamSubscription? _eventSub; - // 颜色常量 - static const upColor = Color(0xFF00C853); - static const downColor = Color(0xFFFF5252); - @override void initState() { super.initState(); @@ -54,44 +54,83 @@ class _FundOrdersPageState extends State { @override Widget build(BuildContext context) { - final theme = ShadTheme.of(context); + final isDark = Theme.of(context).brightness == Brightness.dark; + final bgColor = isDark + ? AppColorScheme.darkBackground + : AppColorScheme.lightBackground; return Scaffold( - backgroundColor: theme.colorScheme.background, + backgroundColor: bgColor, appBar: AppBar( - title: const Text('充提记录'), - backgroundColor: theme.colorScheme.background, + leading: IconButton( + icon: const Icon(LucideIcons.arrowLeft, size: 20), + onPressed: () => Navigator.of(context).pop(), + ), + title: Text( + '充提记录', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + backgroundColor: bgColor, elevation: 0, + scrolledUnderElevation: 0, + centerTitle: true, ), body: Column( children: [ - _buildTabs(), - Expanded(child: _buildOrderList()), + _buildFilterTabs(context, isDark), + Expanded(child: _buildOrderList(context, isDark)), ], ), ); } - Widget _buildTabs() { - final theme = ShadTheme.of(context); + // --------------------------------------------------------------------------- + // Filter Tabs - pill-style segmented control + // --------------------------------------------------------------------------- + Widget _buildFilterTabs(BuildContext context, bool isDark) { + final bgColor = isDark + ? AppColorScheme.darkSurfaceContainerHigh + : AppColorScheme.lightSurfaceHigh; + final activeBgColor = isDark + ? AppColorScheme.darkOnSurface + : Colors.white; - return Container( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - _buildTab('全部', 0), - const SizedBox(width: 12), - _buildTab('充值', 1), - const SizedBox(width: 12), - _buildTab('提现', 2), - ], + return Padding( + padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), + child: Container( + height: 40, + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: bgColor, + borderRadius: AppRadius.radiusMd, + ), + child: Row( + children: [ + _buildPillTab('全部', 0, activeBgColor, isDark), + _buildPillTab('充值', 1, activeBgColor, isDark), + _buildPillTab('提现', 2, activeBgColor, isDark), + ], + ), ), ); } - Widget _buildTab(String label, int index) { - final theme = ShadTheme.of(context); + Widget _buildPillTab( + String label, + int index, + Color activeBgColor, + bool isDark, + ) { final isActive = _activeTab == index; + final activeTextColor = isDark + ? AppColorScheme.darkBackground + : AppColorScheme.lightOnSurface; + final inactiveTextColor = isDark + ? AppColorScheme.darkOnSurfaceVariant + : AppColorScheme.lightOnSurfaceVariant; return Expanded( child: GestureDetector( @@ -102,20 +141,17 @@ class _FundOrdersPageState extends State { } }, child: Container( - padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( - color: isActive ? theme.colorScheme.primary : theme.colorScheme.card, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: isActive ? theme.colorScheme.primary : theme.colorScheme.border, - ), + color: isActive ? activeBgColor : Colors.transparent, + borderRadius: AppRadius.radiusSm, ), child: Center( child: Text( label, - style: TextStyle( - color: isActive ? Colors.white : theme.colorScheme.mutedForeground, - fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, + color: isActive ? activeTextColor : inactiveTextColor, ), ), ), @@ -124,7 +160,10 @@ class _FundOrdersPageState extends State { ); } - Widget _buildOrderList() { + // --------------------------------------------------------------------------- + // Order List + // --------------------------------------------------------------------------- + Widget _buildOrderList(BuildContext context, bool isDark) { return Consumer( builder: (context, provider, _) { final orders = provider.fundOrders; @@ -142,12 +181,19 @@ class _FundOrdersPageState extends State { Icon( LucideIcons.inbox, size: 64, - color: Colors.grey[400], + color: isDark + ? AppColorScheme.darkOnSurfaceMuted + : AppColorScheme.lightOnSurfaceMuted, ), const SizedBox(height: 16), Text( '暂无订单记录', - style: TextStyle(color: Colors.grey[600]), + style: GoogleFonts.inter( + fontSize: 14, + color: isDark + ? AppColorScheme.darkOnSurfaceVariant + : AppColorScheme.lightOnSurfaceVariant, + ), ), ], ), @@ -157,11 +203,11 @@ class _FundOrdersPageState extends State { return RefreshIndicator( onRefresh: () async => _loadData(), child: ListView.separated( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), itemCount: orders.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { - return _buildOrderCard(orders[index]); + return _buildOrderCard(orders[index], isDark); }, ), ); @@ -169,190 +215,392 @@ class _FundOrdersPageState extends State { ); } - Widget _buildOrderCard(OrderFund order) { - final theme = ShadTheme.of(context); - final isDeposit = order.isDeposit; + // --------------------------------------------------------------------------- + // Order Card + // --------------------------------------------------------------------------- + Widget _buildOrderCard(OrderFund order, bool isDark) { + final cardBg = isDark + ? AppColorScheme.darkSurfaceContainer + : AppColorScheme.lightSurfaceLowest; + final borderColor = isDark + ? AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15) + : AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5); + final primaryText = isDark + ? AppColorScheme.darkOnSurface + : AppColorScheme.lightOnSurface; + final mutedText = isDark + ? AppColorScheme.darkOnSurfaceMuted + : AppColorScheme.lightOnSurfaceMuted; - return ShadCard( + return Container( padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: cardBg, + borderRadius: AppRadius.radiusLg, + border: Border.all(color: borderColor, width: 1), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: isDeposit - ? upColor.withValues(alpha: 0.1) - : downColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), - ), - child: Text( - order.typeText, - style: TextStyle( - color: isDeposit ? upColor : downColor, - fontSize: 12, - fontWeight: FontWeight.w600, - ), - ), - ), - const SizedBox(width: 8), - _buildStatusBadge(order), - ], - ), - Text( - order.orderNo, - style: const TextStyle(fontSize: 11, color: Colors.grey), - ), - ], - ), + // Header: type badge + status badge + _buildCardHeader(order, isDark), const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${isDeposit ? '+' : '-'}${order.amount} USDT', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: isDeposit ? upColor : downColor, - ), - ), - if (order.canCancel || order.canConfirmPay) - Row( - children: [ - if (order.canConfirmPay) - ShadButton.outline( - size: ShadButtonSize.sm, - onPressed: () => _confirmPay(order), - child: const Text('已打款'), - ), - if (order.canCancel) ...[ - const SizedBox(width: 8), - ShadButton.destructive( - size: ShadButtonSize.sm, - onPressed: () => _cancelOrder(order), - child: const Text('取消'), - ), - ], - ], - ), - ], - ), + // Amount + _buildAmountRow(order, primaryText), const SizedBox(height: 12), - // 显示地址信息 - if (order.walletAddress != null) ...[ - const Divider(), + // Detail rows + _buildDetailRows(order, primaryText, mutedText), + // Rejection reason + if (order.rejectReason != null) ...[ const SizedBox(height: 8), - Row( - children: [ - Text( - '${isDeposit ? '充值地址' : '提现地址'}: ', - style: const TextStyle(fontSize: 12, color: Colors.grey), - ), - Expanded( - child: Text( - order.walletAddress!, - style: const TextStyle(fontSize: 11, fontFamily: 'monospace'), - overflow: TextOverflow.ellipsis, - ), - ), - GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: order.walletAddress!)); - ToastUtils.show('地址已复制'); - }, - child: Icon(LucideIcons.copy, size: 14, color: Colors.grey[600]), - ), - ], - ), + _buildRejectionReason(order), ], - if (order.withdrawContact != null) ...[ - const SizedBox(height: 4), - Text( - '联系方式: ${order.withdrawContact}', - style: const TextStyle(fontSize: 12, color: Colors.grey), - ), + // Payable amount (withdrawal with fee) + if (order.receivableAmount != null && !order.isDeposit) ...[ + const SizedBox(height: 8), + _buildPayableRow(order, isDark, primaryText), ], - const SizedBox(height: 8), + // Action buttons + if (order.canCancel || order.canConfirmPay) ...[ + const SizedBox(height: 12), + _buildActions(order, isDark), + ], + ], + ), + ); + } + + // --------------------------------------------------------------------------- + // Card Header - type badge + status badge + // --------------------------------------------------------------------------- + Widget _buildCardHeader(OrderFund order, bool isDark) { + final upColor = AppColorScheme.getUpColor(isDark); + final downColor = AppColorScheme.getDownColor(isDark); + final upBg = AppColorScheme.getUpBackgroundColor(isDark, opacity: 0.12); + final downBg = AppColorScheme.getDownBackgroundColor(isDark, opacity: 0.12); + final typeColor = order.isDeposit ? upColor : downColor; + final typeBg = order.isDeposit ? upBg : downBg; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Type badge (充值 / 提现) + _buildBadge(order.typeText, typeColor, typeBg), + // Status badge + _buildStatusBadge(order, isDark), + ], + ); + } + + Widget _buildStatusBadge(OrderFund order, bool isDark) { + final upColor = AppColorScheme.getUpColor(isDark); + final downColor = AppColorScheme.getDownColor(isDark); + final upBg = AppColorScheme.getUpBackgroundColor(isDark, opacity: 0.12); + final downBg = AppColorScheme.getDownBackgroundColor(isDark, opacity: 0.12); + const amberColor = Color(0xFFD97706); + const amberBg = Color(0xFFFEF3C7); + + Color bgColor; + Color textColor; + + if (order.isDeposit) { + switch (order.status) { + case 1: // 待付款 + case 2: // 待确认 + bgColor = amberBg; + textColor = amberColor; + break; + case 3: // 已完成 + bgColor = upBg; + textColor = upColor; + break; + default: // 已驳回/已取消 + bgColor = downBg; + textColor = downColor; + } + } else { + switch (order.status) { + case 1: // 待审批 + case 5: // 待财务审核 + bgColor = amberBg; + textColor = amberColor; + break; + case 2: // 已完成 + bgColor = upBg; + textColor = upColor; + break; + default: // 已驳回/已取消 + bgColor = downBg; + textColor = downColor; + } + } + + return _buildBadge(order.statusText, textColor, bgColor); + } + + Widget _buildBadge(String text, Color textColor, Color bgColor) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + text, + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ); + } + + // --------------------------------------------------------------------------- + // Amount Row + // --------------------------------------------------------------------------- + Widget _buildAmountRow(OrderFund order, Color primaryText) { + return Text( + '${order.isDeposit ? '+' : '-'}${order.amount} USDT', + style: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w700, + color: primaryText, + ), + ); + } + + // --------------------------------------------------------------------------- + // Detail Rows + // --------------------------------------------------------------------------- + Widget _buildDetailRows( + OrderFund order, + Color primaryText, + Color mutedText, + ) { + return Column( + children: [ + // Order number + _buildDetailRow('订单号', order.orderNo, primaryText, mutedText), + const SizedBox(height: 6), + // Network / wallet address + if (order.walletAddress != null) ...[ + _buildDetailRow( + '网络', + order.remark.isNotEmpty ? order.remark : '-', + primaryText, + mutedText, + ), + const SizedBox(height: 6), + _buildDetailRow( + '地址', + _truncateAddress(order.walletAddress!), + primaryText, + mutedText, + trailing: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: order.walletAddress!)); + ToastUtils.show('地址已复制'); + }, + child: Icon( + LucideIcons.copy, + size: 14, + color: mutedText, + ), + ), + ), + const SizedBox(height: 6), + ] else if (order.remark.isNotEmpty) ...[ + _buildDetailRow('网络', order.remark, primaryText, mutedText), + const SizedBox(height: 6), + ], + // Fee (withdrawal) + if (order.fee != null && !order.isDeposit) ...[ + _buildDetailRow('手续费', '${order.fee}%', primaryText, mutedText), + const SizedBox(height: 6), + ], + // Time + _buildDetailRow( + '时间', + _formatTime(order.createTime), + primaryText, + mutedText, + ), + ], + ); + } + + Widget _buildDetailRow( + String label, + String value, + Color primaryText, + Color mutedText, { + Widget? trailing, + }) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: mutedText, + ), + ), + if (trailing != null) Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, children: [ Text( - '创建: ${_formatTime(order.createTime)}', - style: const TextStyle(fontSize: 11, color: Colors.grey), - ), - if (order.rejectReason != null) - Expanded( - child: Text( - '驳回: ${order.rejectReason}', - style: const TextStyle(fontSize: 11, color: downColor), - textAlign: TextAlign.right, - overflow: TextOverflow.ellipsis, - ), + value, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: primaryText, ), + ), + const SizedBox(width: 4), + trailing, ], + ) + else + Text( + value, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: primaryText, + ), + ), + ], + ); + } + + // --------------------------------------------------------------------------- + // Rejection Reason + // --------------------------------------------------------------------------- + Widget _buildRejectionReason(OrderFund order) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return Text( + '拒绝原因: ${order.rejectReason}', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: AppColorScheme.getDownColor(isDark), + ), + ); + } + + // --------------------------------------------------------------------------- + // Payable Amount Row (withdrawal) + // --------------------------------------------------------------------------- + Widget _buildPayableRow( + OrderFund order, + bool isDark, + Color primaryText, + ) { + final bgTertiary = isDark + ? AppColorScheme.darkSurfaceContainerHigh + : AppColorScheme.lightSurfaceHigh; + final secondaryText = isDark + ? AppColorScheme.darkOnSurfaceVariant + : AppColorScheme.lightOnSurfaceVariant; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: bgTertiary, + borderRadius: AppRadius.radiusSm, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '应付金额', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: secondaryText, + ), + ), + Text( + '${order.receivableAmount} USDT', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w600, + color: primaryText, + ), ), ], ), ); } - Widget _buildStatusBadge(OrderFund order) { - Color bgColor; - Color textColor; + // --------------------------------------------------------------------------- + // Action Buttons + // --------------------------------------------------------------------------- + Widget _buildActions(OrderFund order, bool isDark) { + final upColor = AppColorScheme.getUpColor(isDark); + final downColor = AppColorScheme.getDownColor(isDark); - // 根据类型和状态设置颜色 - if (order.type == 1) { - // 充值状态 - switch (order.status) { - case 1: // 待付款 - case 2: // 待确认 - bgColor = Colors.orange.withValues(alpha: 0.1); - textColor = Colors.orange; - break; - case 3: // 已完成 - bgColor = upColor.withValues(alpha: 0.1); - textColor = upColor; - break; - default: // 已驳回/已取消 - bgColor = downColor.withValues(alpha: 0.1); - textColor = downColor; - } - } else { - // 提现状态 - switch (order.status) { - case 1: // 待审批 - bgColor = Colors.orange.withValues(alpha: 0.1); - textColor = Colors.orange; - break; - case 2: // 已完成 - bgColor = upColor.withValues(alpha: 0.1); - textColor = upColor; - break; - default: // 已驳回/已取消 - bgColor = downColor.withValues(alpha: 0.1); - textColor = downColor; - } - } - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: bgColor, - borderRadius: BorderRadius.circular(4), - ), - child: Text( - order.statusText, - style: TextStyle(fontSize: 11, color: textColor), - ), + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (order.canCancel) + GestureDetector( + onTap: () => _cancelOrder(order), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + borderRadius: AppRadius.radiusSm, + border: Border.all(color: downColor, width: 1), + ), + child: Text( + '取消订单', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: downColor, + ), + ), + ), + ), + if (order.canCancel && order.canConfirmPay) + const SizedBox(width: 12), + if (order.canConfirmPay) + GestureDetector( + onTap: () => _confirmPay(order), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: upColor, + borderRadius: AppRadius.radiusSm, + ), + child: Text( + '已打款', + style: GoogleFonts.inter( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ), + ], ); } + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + String _truncateAddress(String address) { + if (address.length > 12) { + return '${address.substring(0, 4)}...${address.substring(address.length - 4)}'; + } + return address; + } + String _formatTime(DateTime? time) { if (time == null) return '-'; return '${time.year}-${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} ' @@ -360,60 +608,58 @@ class _FundOrdersPageState extends State { } void _confirmPay(OrderFund order) async { - final confirmed = await showShadDialog( + final confirmed = await showShadConfirmDialog( context: context, - builder: (context) => ShadDialog.alert( - title: const Text('确认已打款'), - description: const Text('确认您已完成向指定地址的转账?'), - actions: [ - ShadButton.outline( - child: const Text('取消'), - onPressed: () => Navigator.pop(context, false), - ), - ShadButton( - child: const Text('确认'), - onPressed: () => Navigator.pop(context, true), - ), - ], - ), + title: '确认已打款', + description: '确认您已完成向指定地址的转账?', ); if (confirmed == true && mounted) { final response = await context.read().confirmPay(order.orderNo); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(response.success ? '确认成功,请等待审核' : response.message ?? '确认失败')), - ); + BotToast.showText(text: response.success ? '确认成功,请等待审核' : response.message ?? '确认失败'); } } } void _cancelOrder(OrderFund order) async { - final confirmed = await showShadDialog( + final confirmed = await showShadConfirmDialog( context: context, - builder: (context) => ShadDialog.alert( - title: const Text('取消订单'), - description: Text('确定要取消订单 ${order.orderNo} 吗?'), - actions: [ - ShadButton.outline( - child: const Text('返回'), - onPressed: () => Navigator.pop(context, false), - ), - ShadButton.destructive( - child: const Text('确定取消'), - onPressed: () => Navigator.pop(context, true), - ), - ], - ), + title: '取消订单', + description: '确定要取消订单 ${order.orderNo} 吗?', + destructive: true, ); if (confirmed == true && mounted) { final response = await context.read().cancelOrder(order.orderNo); if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(response.success ? '订单已取消' : response.message ?? '取消失败')), - ); + BotToast.showText(text: response.success ? '订单已取消' : response.message ?? '取消失败'); } } } + + Future showShadConfirmDialog({ + required BuildContext context, + required String title, + required String description, + bool destructive = false, + }) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(description), + actions: [ + TextButton( + child: const Text('取消'), + onPressed: () => Navigator.pop(context, false), + ), + TextButton( + child: Text(destructive ? '确定取消' : '确认'), + onPressed: () => Navigator.pop(context, true), + ), + ], + ), + ); + } } diff --git a/flutter_monisuo/lib/ui/pages/trade/trade_page.dart b/flutter_monisuo/lib/ui/pages/trade/trade_page.dart index 0c2d65c..743d725 100644 --- a/flutter_monisuo/lib/ui/pages/trade/trade_page.dart +++ b/flutter_monisuo/lib/ui/pages/trade/trade_page.dart @@ -2,6 +2,7 @@ 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 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../data/models/coin.dart'; @@ -12,6 +13,13 @@ import '../../components/glass_panel.dart'; import '../../components/neon_glow.dart'; /// 交易页面 +/// +/// 设计稿 Trade 页面,布局结构: +/// - 币种选择器卡片(Coin Selector Card) +/// - 价格卡片(Price Card):大号价格 + 涨跌幅徽章 + 副标题 +/// - 买入/卖出切换(Buy/Sell Toggle) +/// - 交易表单卡片(Trade Form Card):金额输入 + 快捷比例 + 计算数量 +/// - CTA 买入/卖出按钮(Buy/Sell Button) class TradePage extends StatefulWidget { final String? initialCoinCode; @@ -108,86 +116,73 @@ class _TradePageState extends State backgroundColor: colorScheme.background, body: Consumer2( builder: (context, market, asset, _) { - return SingleChildScrollView( - padding: AppSpacing.pagePadding, - child: Column( - children: [ - // 币种选择器 - _CoinSelector( - selectedCoin: _selectedCoin, - coins: market.allCoins - .where((c) => - c.code != 'USDT' && - c.code != 'BTC' && - c.code != 'ETH') - .toList(), - onCoinSelected: (coin) { - setState(() { - _selectedCoin = coin; + return SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB( + AppSpacing.md, 0, AppSpacing.md, AppSpacing.xl + AppSpacing.sm, + ), + child: Column( + children: [ + // 币种选择器卡片 + _CoinSelector( + selectedCoin: _selectedCoin, + coins: market.allCoins + .where((c) => + c.code != 'USDT' && + c.code != 'BTC' && + c.code != 'ETH') + .toList(), + onCoinSelected: (coin) { + setState(() { + _selectedCoin = coin; + _amountController.clear(); + }); + }, + ), + const SizedBox(height: AppSpacing.md), + + // 价格卡片 + if (_selectedCoin != null) + _PriceCard(coin: _selectedCoin!) + else + _PlaceholderCard( + message: '请先选择交易币种', + colorScheme: colorScheme, + ), + const SizedBox(height: AppSpacing.md), + + // 交易表单卡片(内含买入/卖出切换 + 表单) + _TradeFormCard( + tradeType: _tradeType, + selectedCoin: _selectedCoin, + amountController: _amountController, + availableUsdt: _availableUsdt, + availableCoinQty: _availableCoinQty, + calculatedQuantity: _calculatedQuantity, + maxAmount: _maxAmount, + onTradeTypeChanged: (type) => setState(() { + _tradeType = type; _amountController.clear(); - }); - }, - ), - SizedBox(height: AppSpacing.md), + }), + onAmountChanged: () => setState(() {}), + onFillPercent: (pct) => _fillPercent(pct), + ), + const SizedBox(height: AppSpacing.md), - // 价格卡片 - if (_selectedCoin != null) - _PriceCard(coin: _selectedCoin!) - else - const _PlaceholderCard(message: '请先选择交易币种'), - - SizedBox(height: AppSpacing.md), - - // 交易表单 - _TradeFormCard( - tradeType: _tradeType, - selectedCoin: _selectedCoin, - amountController: _amountController, - availableUsdt: _availableUsdt, - availableCoinQty: _availableCoinQty, - calculatedQuantity: _calculatedQuantity, - maxAmount: _maxAmount, - onTradeTypeChanged: (type) => setState(() { - _tradeType = type; - _amountController.clear(); - }), - onAmountChanged: () => setState(() {}), - onFillPercent: (pct) => _fillPercent(pct), - ), - - SizedBox(height: AppSpacing.lg), - - // 买入 + 卖出双按钮 - Row( - children: [ - Expanded( - child: _TradeButton( - isBuy: true, - coinCode: _selectedCoin?.code, - enabled: _canTrade() && !_isSubmitting, - isLoading: _isSubmitting && _tradeType == 0, - onPressed: () { - _tradeType = 0; - _executeTrade(); - }, - ), + // CTA 买入/卖出按钮 + SizedBox( + width: double.infinity, + height: 48, + child: _TradeButton( + isBuy: _tradeType == 0, + coinCode: _selectedCoin?.code, + enabled: _canTrade() && !_isSubmitting, + isLoading: _isSubmitting, + onPressed: _executeTrade, ), - SizedBox(width: AppSpacing.md), - Expanded( - child: _TradeButton( - isBuy: false, - coinCode: _selectedCoin?.code, - enabled: _canTrade() && !_isSubmitting, - isLoading: _isSubmitting && _tradeType == 1, - onPressed: () { - _tradeType = 1; - _executeTrade(); - }, - ), - ), - ], - ), - ], + ), + ], + ), ), ); }, @@ -330,7 +325,7 @@ class _ConfirmDialog extends StatelessWidget { Center( child: Text( '确认${isBuy ? '买入' : '卖出'}', - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.bold, color: colorScheme.onSurface, @@ -382,10 +377,13 @@ class _ConfirmDialog extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, - style: TextStyle( - fontSize: 14, color: colorScheme.onSurfaceVariant)), + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + )), Text(value, - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w600, color: valueColor ?? colorScheme.onSurface, @@ -395,7 +393,12 @@ class _ConfirmDialog extends StatelessWidget { } } -/// 币种选择器 +// ============================================ +// 币种选择器 - 设计稿 Coin Selector Card +// card背景 + 圆角lg + border + padding:16 +// 横向布局:coinInfo(竖向 pair+name) + chevronDown +// ============================================ + class _CoinSelector extends StatelessWidget { final Coin? selectedCoin; final List coins; @@ -410,41 +413,54 @@ class _CoinSelector extends StatelessWidget { @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; return GestureDetector( onTap: () => _showCoinPicker(context), - child: GlassPanel( - padding: EdgeInsets.all(AppSpacing.md), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(AppSpacing.md), + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerLowest, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, 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, - ), + // 币种信息:交易对 + 名称 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + selectedCoin != null + ? '${selectedCoin!.code}/USDT' + : '选择币种', + style: GoogleFonts.inter( + fontSize: 18, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, ), - SizedBox(height: AppSpacing.xs), - Text( - selectedCoin?.name ?? '点击选择交易对', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), + ), + const SizedBox(height: 2), + Text( + selectedCoin?.name ?? '点击选择交易对', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, ), - ], - ), + ), + ], ), - Icon(LucideIcons.chevronDown, color: colorScheme.onSurfaceVariant), + // 下拉箭头 + Icon(LucideIcons.chevronDown, + size: 16, color: colorScheme.onSurfaceVariant), ], ), ), @@ -470,6 +486,7 @@ class _CoinSelector extends StatelessWidget { ), child: Column( children: [ + // 拖动指示器 Container( margin: EdgeInsets.only(top: AppSpacing.sm), width: 40, @@ -479,13 +496,14 @@ class _CoinSelector extends StatelessWidget { borderRadius: BorderRadius.circular(2), ), ), + // 标题栏 Padding( padding: EdgeInsets.all(AppSpacing.lg), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('选择币种', - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.bold, color: colorScheme.onSurface, @@ -499,6 +517,7 @@ class _CoinSelector extends StatelessWidget { ), ), Divider(height: 1, color: colorScheme.outlineVariant.withOpacity(0.2)), + // 币种列表 Expanded( child: ListView.builder( padding: EdgeInsets.symmetric(vertical: AppSpacing.sm), @@ -528,8 +547,8 @@ class _CoinSelector extends StatelessWidget { onCoinSelected(coin); }, child: Container( - padding: - EdgeInsets.symmetric(horizontal: AppSpacing.lg, vertical: AppSpacing.md), + padding: EdgeInsets.symmetric( + horizontal: AppSpacing.lg, vertical: AppSpacing.md), color: isSelected ? colorScheme.primary.withOpacity(0.1) : Colors.transparent, child: Row( @@ -540,38 +559,38 @@ class _CoinSelector extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 第一行:币种代码 + 价格 + 涨跌幅 + // 第一行:币种代码 + USDT + 价格 + 涨跌幅 Row( children: [ Text(coin.code, - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 15, fontWeight: FontWeight.bold, color: colorScheme.onSurface, )), SizedBox(width: AppSpacing.xs), Text('/USDT', - style: TextStyle( + style: GoogleFonts.inter( fontSize: 11, color: colorScheme.onSurfaceVariant, )), const Spacer(), Text('\$${coin.formattedPrice}', - style: GoogleFonts.spaceGrotesk( + style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w600, color: colorScheme.onSurface, )), SizedBox(width: AppSpacing.sm), + // 涨跌幅徽章 Container( - padding: EdgeInsets.symmetric( - horizontal: 6, vertical: 2), + padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: changeColor.withOpacity(0.1), borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Text(coin.formattedChange, - style: TextStyle( + style: GoogleFonts.inter( fontSize: 11, color: changeColor, fontWeight: FontWeight.w600, @@ -587,7 +606,7 @@ class _CoinSelector extends StatelessWidget { SizedBox(height: 3), // 第二行:币种名称 Text(coin.name, - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, color: colorScheme.onSurfaceVariant, )), @@ -629,7 +648,14 @@ class _CoinAvatar extends StatelessWidget { } } -/// 价格卡片 - 重新设计 +// ============================================ +// 价格卡片 - 设计稿 Price Card +// card背景 + 圆角lg + border + padding:20 + gap:8 +// 竖向布局: +// priceRow: 大号价格(32px bold) + 涨跌幅徽章(圆角sm,涨绿背景) +// subtitle: "24h 变化" +// ============================================ + class _PriceCard extends StatelessWidget { final Coin coin; const _PriceCard({required this.coin}); @@ -640,79 +666,67 @@ class _PriceCard extends StatelessWidget { final isDark = Theme.of(context).brightness == Brightness.dark; final isUp = coin.isUp; final changeColor = - isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.down; + isUp ? AppColorScheme.getUpColor(isDark) : AppColorScheme.getDownColor(isDark); final changeBgColor = isUp ? AppColorScheme.getUpBackgroundColor(isDark) - : colorScheme.error.withOpacity(0.1); + : AppColorScheme.getDownBackgroundColor(isDark); - return GlassPanel( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.lg, vertical: AppSpacing.md + AppSpacing.sm), - child: Row( + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerLowest, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 左侧:币种标签 + 价格 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.sm, vertical: 2), - decoration: BoxDecoration( - color: colorScheme.primary.withOpacity(0.08), - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - child: Text( - '${coin.code}/USDT', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: colorScheme.primary, - ), - ), - ), - ], + // 价格行:大号价格 + 涨跌幅徽章 + Row( + children: [ + Text( + coin.formattedPrice, + style: GoogleFonts.inter( + fontSize: 32, + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, + fontFeatures: [FontFeature.tabularFigures()], ), - SizedBox(height: AppSpacing.sm), - Text( - '\$${coin.formattedPrice}', - style: GoogleFonts.spaceGrotesk( - fontSize: 20, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), + ), + const SizedBox(width: AppSpacing.sm), + // 涨跌幅徽章 - 圆角sm,涨绿背景 + Container( + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.sm, vertical: AppSpacing.xs), + decoration: BoxDecoration( + color: changeBgColor, + borderRadius: BorderRadius.circular(AppRadius.sm), ), - ], - ), - ), - // 右侧:涨跌幅 - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, vertical: AppSpacing.sm + 2), - decoration: BoxDecoration( - color: changeBgColor, - borderRadius: BorderRadius.circular(AppRadius.lg), - border: Border.all(color: changeColor.withOpacity(0.15)), - ), - child: Column( - children: [ - Text( - '24h', - style: TextStyle( - fontSize: 10, - color: changeColor.withOpacity(0.7), - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 2), - Text( + child: Text( coin.formattedChange, - style: TextStyle( - fontSize: 16, color: changeColor, fontWeight: FontWeight.w700), + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.w600, + color: changeColor, + fontFeatures: [FontFeature.tabularFigures()], + ), ), - ], + ), + ], + ), + const SizedBox(height: AppSpacing.sm), + // 副标题 + Text( + '24h 变化', + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, ), ), ], @@ -724,16 +738,27 @@ class _PriceCard extends StatelessWidget { /// 占位卡片 class _PlaceholderCard extends StatelessWidget { final String message; - const _PlaceholderCard({required this.message}); + final ColorScheme colorScheme; + const _PlaceholderCard({required this.message, required this.colorScheme}); @override Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.xl), + final isDark = Theme.of(context).brightness == Brightness.dark; + return Container( + width: double.infinity, + padding: const EdgeInsets.all(AppSpacing.xl), + decoration: BoxDecoration( + color: isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerLowest, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), child: Center( child: Text(message, - style: TextStyle( + style: GoogleFonts.inter( color: colorScheme.onSurfaceVariant, fontSize: 14, )), @@ -742,7 +767,18 @@ class _PlaceholderCard extends StatelessWidget { } } -/// 交易表单卡片 - 重新设计 +// ============================================ +// 交易表单卡片 - 设计稿 Trade Form Card +// card背景 + 圆角lg + border + padding:20 + gap:16 +// 竖向布局: +// Buy/Sell Toggle(圆角md,clip,横向两等宽按钮) +// 金额label行("交易金额" + "USDT") +// 输入框(bg-tertiary,圆角md,高48) +// 可用余额文字 +// 快捷比例按钮行(25% 50% 75% 100%,gap:8) +// 计算数量行 +// ============================================ + class _TradeFormCard extends StatelessWidget { final int tradeType; final Coin? selectedCoin; @@ -777,66 +813,120 @@ class _TradeFormCard extends StatelessWidget { ? AppColorScheme.getUpColor(isDark) : AppColorScheme.getDownColor(isDark); - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg), + // 设计稿中 card 背景色 + final cardBgColor = isDark + ? colorScheme.surfaceContainer + : colorScheme.surfaceContainerLowest; + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cardBgColor, + borderRadius: BorderRadius.circular(AppRadius.lg), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15), + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 买入/卖出切换 - 重新设计 - Container( - padding: EdgeInsets.all(4), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerLowest, - borderRadius: BorderRadius.circular(AppRadius.xl), - ), + // ---- 买入/卖出切换 ---- + // 设计稿:ClipRRect + 圆角md,两等宽按钮 + ClipRRect( + borderRadius: BorderRadius.circular(AppRadius.md), child: Row( children: [ + // 买入按钮 Expanded( - child: _buildTypeButton( - context: context, - label: '买入', - isActive: isBuy, - color: AppColorScheme.buyButtonFill, - icon: LucideIcons.trendingUp, + child: GestureDetector( onTap: () => onTradeTypeChanged(0), + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + height: 40, + decoration: BoxDecoration( + color: isBuy + ? AppColorScheme.buyButtonFill + : cardBgColor, + border: isBuy + ? null + : Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15)), + ), + child: Center( + child: Text( + '买入', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isBuy + ? Colors.white + : colorScheme.onSurfaceVariant, + ), + ), + ), + ), ), ), - SizedBox(width: 4), + // 卖出按钮 Expanded( - child: _buildTypeButton( - context: context, - label: '卖出', - isActive: !isBuy, - color: AppColorScheme.sellButtonFill, - icon: LucideIcons.trendingDown, + child: GestureDetector( onTap: () => onTradeTypeChanged(1), + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + height: 40, + decoration: BoxDecoration( + color: !isBuy + ? AppColorScheme.sellButtonFill + : cardBgColor, + border: !isBuy + ? null + : Border.all( + color: colorScheme.outlineVariant.withOpacity(0.15)), + ), + child: Center( + child: Text( + '卖出', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: !isBuy + ? Colors.white + : colorScheme.onSurfaceVariant, + ), + ), + ), + ), ), ), ], ), ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.md + AppSpacing.sm), - // 交易金额输入 + // ---- 交易金额 label 行 ---- Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('交易金额', - style: TextStyle( - fontSize: 13, + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + )), + Text('USDT', + style: GoogleFonts.inter( + fontSize: 12, fontWeight: FontWeight.w600, color: colorScheme.onSurface, )), - Text('USDT', - style: GoogleFonts.spaceGrotesk( - fontSize: 11, - fontWeight: FontWeight.w700, - color: actionColor.withOpacity(0.7), - letterSpacing: 0.5, - )), ], ), - SizedBox(height: AppSpacing.sm), + const SizedBox(height: AppSpacing.sm), + + // ---- 金额输入框 ---- _AmountInput( amountController: amountController, maxAmount: maxAmount, @@ -844,153 +934,79 @@ class _TradeFormCard extends StatelessWidget { actionColor: actionColor, onChanged: onAmountChanged, ), - SizedBox(height: AppSpacing.sm), + const SizedBox(height: AppSpacing.sm), - // 快捷比例按钮 - 药丸样式 + // ---- 可用余额 ---- + Text( + isBuy + ? '可用: $availableUsdt USDT' + : '可用: $availableCoinQty ${selectedCoin?.code ?? ""}', + style: GoogleFonts.inter( + fontSize: 11, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + ), + ), + const SizedBox(height: AppSpacing.md), + + // ---- 快捷比例按钮 25% 50% 75% 100% ---- + // 设计稿:gap:8,圆角sm,bg-tertiary,高32 Row( children: [ - _buildPctButton('25%', 0.25, colorScheme, actionColor), - SizedBox(width: 6), - _buildPctButton('50%', 0.5, colorScheme, actionColor), - SizedBox(width: 6), - _buildPctButton('75%', 0.75, colorScheme, actionColor), - SizedBox(width: 6), - _buildPctButton('全部', 1.0, colorScheme, actionColor), + _buildPctButton('25%', 0.25, colorScheme), + const SizedBox(width: AppSpacing.sm), + _buildPctButton('50%', 0.5, colorScheme), + const SizedBox(width: AppSpacing.sm), + _buildPctButton('75%', 0.75, colorScheme), + const SizedBox(width: AppSpacing.sm), + _buildPctButton('100%', 1.0, colorScheme), ], ), - SizedBox(height: AppSpacing.lg), + const SizedBox(height: AppSpacing.md + AppSpacing.sm), - // 预计数量 + 可用余额 - 卡片样式 - Container( - padding: EdgeInsets.all(AppSpacing.md), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerLowest, - borderRadius: BorderRadius.circular(AppRadius.lg), - border: Border.all( - color: colorScheme.outlineVariant.withOpacity(0.1)), - ), - child: Column( - children: [ - // 预计数量 - Row( - children: [ - Icon(LucideIcons.calculator, - size: 14, color: colorScheme.onSurfaceVariant), - SizedBox(width: AppSpacing.sm), - Text('预计数量', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - )), - const Spacer(), - Text( - '$calculatedQuantity ${selectedCoin?.code ?? ''}', - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - ], + // ---- 计算数量行 ---- + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('交易数量', + style: GoogleFonts.inter( + fontSize: 12, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant, + )), + Text( + '$calculatedQuantity ${selectedCoin?.code ?? ''}', + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + fontFeatures: [FontFeature.tabularFigures()], ), - Padding( - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm), - child: Divider( - height: 1, - color: colorScheme.outlineVariant.withOpacity(0.08)), - ), - // 可用余额 - Row( - children: [ - Icon(LucideIcons.wallet, - size: 14, color: colorScheme.onSurfaceVariant), - SizedBox(width: AppSpacing.sm), - Text(isBuy ? '可用 USDT' : '可用 ${selectedCoin?.code ?? ""}', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - )), - const Spacer(), - Text( - isBuy - ? '$availableUsdt USDT' - : '$availableCoinQty ${selectedCoin?.code ?? ""}', - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - ], - ), - ], - ), + ), + ], ), ], ), ); } - /// 买入/卖出切换按钮 - 实心填充 + 图标 - Widget _buildTypeButton({ - required BuildContext context, - required String label, - required bool isActive, - required Color color, - required IconData icon, - required VoidCallback onTap, - }) { - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + 4), - decoration: BoxDecoration( - color: isActive ? color : Colors.transparent, - borderRadius: BorderRadius.circular(AppRadius.lg), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, - size: 16, - color: isActive ? Colors.white : color.withOpacity(0.5)), - SizedBox(width: AppSpacing.xs), - Text( - label, - style: TextStyle( - color: isActive ? Colors.white : color.withOpacity(0.5), - fontWeight: FontWeight.w700, - fontSize: 15, - letterSpacing: 0.5, - ), - ), - ], - ), - ), - ); - } - - /// 百分比按钮 - 药丸样式 - Widget _buildPctButton( - String label, double pct, ColorScheme colorScheme, Color actionColor) { + /// 百分比按钮 - 设计稿:圆角sm,bg-tertiary,高32 + Widget _buildPctButton(String label, double pct, ColorScheme colorScheme) { return Expanded( child: GestureDetector( onTap: () => onFillPercent(pct), child: Container( - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm - 2), + height: 32, decoration: BoxDecoration( - color: actionColor.withOpacity(0.06), - borderRadius: BorderRadius.circular(AppRadius.full), - border: Border.all(color: actionColor.withOpacity(0.12)), + color: colorScheme.surfaceContainerHighest.withOpacity(0.5), + borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Center( child: Text(label, - style: TextStyle( + style: GoogleFonts.inter( fontSize: 12, - fontWeight: FontWeight.w600, - color: actionColor.withOpacity(0.8), + fontWeight: FontWeight.w500, + color: colorScheme.onSurfaceVariant, )), ), ), @@ -999,7 +1015,11 @@ class _TradeFormCard extends StatelessWidget { } } -/// 交易按钮 - 使用 NeonButton 风格 +// ============================================ +// CTA 交易按钮 - 设计稿 Buy Button +// profit-green底 / sell-red底,圆角lg,高48,白字16px bold +// ============================================ + class _TradeButton extends StatelessWidget { final bool isBuy; final String? coinCode; @@ -1025,7 +1045,7 @@ class _TradeButton extends StatelessWidget { onTap: enabled ? onPressed : null, child: AnimatedContainer( duration: const Duration(milliseconds: 200), - height: 52, + height: 48, decoration: BoxDecoration( color: enabled ? fillColor : colorScheme.onSurface.withOpacity(0.08), borderRadius: BorderRadius.circular(AppRadius.lg), @@ -1040,29 +1060,15 @@ class _TradeButton extends StatelessWidget { color: Colors.white, ), ) - : Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - isBuy ? LucideIcons.trendingUp : LucideIcons.trendingDown, - size: 16, - color: enabled - ? Colors.white - : colorScheme.onSurface.withOpacity(0.3), - ), - SizedBox(width: AppSpacing.xs), - Text( - '${isBuy ? '买入' : '卖出'} ${coinCode ?? ""}', - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.w700, - color: enabled - ? Colors.white - : colorScheme.onSurface.withOpacity(0.3), - letterSpacing: 0.5, - ), - ), - ], + : Text( + '${isBuy ? '买入' : '卖出'} ${coinCode ?? ""}', + style: GoogleFonts.inter( + fontSize: 16, + fontWeight: FontWeight.w700, + color: enabled + ? Colors.white + : colorScheme.onSurface.withOpacity(0.3), + ), ), ), ), @@ -1070,7 +1076,11 @@ class _TradeButton extends StatelessWidget { } } -/// 金额输入框(含超额提示) +// ============================================ +// 金额输入框(含超额提示) +// 设计稿:bg-tertiary,圆角md,高48 +// ============================================ + class _AmountInput extends StatefulWidget { final TextEditingController amountController; final String maxAmount; @@ -1124,56 +1134,32 @@ class _AmountInputState extends State<_AmountInput> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( + height: 48, decoration: BoxDecoration( - color: colorScheme.surfaceContainerLowest, - borderRadius: BorderRadius.circular(AppRadius.xl), - border: Border.all( - color: _isExceeded - ? warningColor.withOpacity(0.5) - : widget.actionColor.withOpacity(0.15), - ), + color: colorScheme.surfaceContainerHighest.withOpacity(0.3), + borderRadius: BorderRadius.circular(AppRadius.md), ), child: TextField( controller: widget.amountController, keyboardType: const TextInputType.numberWithOptions(decimal: true), onChanged: (_) => _checkLimit(), - style: GoogleFonts.spaceGrotesk( - fontSize: 18, - fontWeight: FontWeight.bold, + style: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.normal, color: colorScheme.onSurface, + fontFeatures: [FontFeature.tabularFigures()], ), decoration: InputDecoration( - hintText: '0.00', - hintStyle: TextStyle( - color: colorScheme.outlineVariant.withOpacity(0.4)), + hintText: '请输入金额', + hintStyle: GoogleFonts.inter( + fontSize: 14, + fontWeight: FontWeight.normal, + color: colorScheme.onSurfaceVariant.withOpacity(0.5), + ), border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( + contentPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.md, - vertical: AppSpacing.md, ), - suffixIcon: Padding( - padding: EdgeInsets.only(right: AppSpacing.sm), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.sm, vertical: AppSpacing.xs), - decoration: BoxDecoration( - color: widget.actionColor.withOpacity(0.08), - borderRadius: BorderRadius.circular(AppRadius.sm), - ), - child: Text('USDT', - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - color: widget.actionColor.withOpacity(0.7), - )), - ), - ], - ), - ), - suffixIconConstraints: const BoxConstraints(minWidth: 60), ), ), ), @@ -1186,7 +1172,10 @@ class _AmountInputState extends State<_AmountInput> { SizedBox(width: 4), Text( '超出可用USDT余额', - style: TextStyle(fontSize: 11, color: warningColor), + style: GoogleFonts.inter( + fontSize: 11, + color: warningColor, + ), ), ], ), diff --git a/flutter_monisuo/lib/ui/shared/ui_constants.dart b/flutter_monisuo/lib/ui/shared/ui_constants.dart index 984647c..470ff03 100644 --- a/flutter_monisuo/lib/ui/shared/ui_constants.dart +++ b/flutter_monisuo/lib/ui/shared/ui_constants.dart @@ -4,7 +4,7 @@ /// 使用方式: import 'ui/shared/ui_constants.dart'; // 导出颜色系统 -export '../../core/constants/app_colors.dart'; +export '../../core/theme/app_color_scheme.dart'; // 导出主题配置 (包含 AppTextStyles, AppSpacing, AppRadius, AppBreakpoints) export '../../core/theme/app_theme.dart';