feat(theme): 重构颜色系统为双主题设计并添加渐变组件
- 引入 "The Kinetic Vault"(深色)和 "The Ethereal Terminal"(浅色)双主题系统 - 重构颜色方案,使用层次化 surface 容器替代边框,遵循无边框设计规则 - 添加渐变预设(CTA、买入/卖出、资产卡片)和渐变按钮组件 - 更新圆角系统,明确按钮、卡片、输入框和标签的圆角规范 - 统一文本样式系统,使用 Space Grotesk(标题)和 Manrope(正文)字体 - 更新现有组件(AssetCard、CoinCard、TradeButton)以遵循新设计规范 - 添加向后兼容的已废弃常量,确保现有代码正常运行
This commit is contained in:
@@ -1,118 +1,171 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
/// 现代化颜色系统 - 支持明暗主题
|
||||
/// "The Kinetic Vault" & "The Ethereal Terminal" 双主题颜色系统
|
||||
///
|
||||
/// 深色主题: "The Kinetic Vault" - 高端加密编辑风格
|
||||
/// 浅色主题: "The Ethereal Terminal" - 高端金融科技风格
|
||||
///
|
||||
/// 设计原则:
|
||||
/// - Vercel/Linear 风格的现代简约设计
|
||||
/// - 深色主题:#0A0A0B 背景,微妙阴影
|
||||
/// - 浅色主题:纯白背景,清晰边框
|
||||
/// - 所有对比度 >= 4.5:1 (WCAG AA)
|
||||
/// - 无边框规则: 禁止 1px solid 边框,使用层次色变化
|
||||
/// - 层次化设计: 通过 surface-container 层次而非阴影
|
||||
/// - 渐变 CTA: primary → primary_container (135度)
|
||||
class AppColorScheme {
|
||||
AppColorScheme._();
|
||||
|
||||
// ============================================
|
||||
// 品牌色 - 青绿色 (明暗通用)
|
||||
// 深色主题 - "The Kinetic Vault"
|
||||
// ============================================
|
||||
|
||||
/// 主品牌色 - 深色主题
|
||||
static const Color primaryDark = Color(0xFF00D4AA);
|
||||
/// 背景基色 - 最深
|
||||
static const Color darkBackground = Color(0xFF0b0e14);
|
||||
|
||||
/// 主品牌色 - 浅色主题
|
||||
static const Color primaryLight = Color(0xFF00B894);
|
||||
/// Surface 层次 (从低到高)
|
||||
static const Color darkSurfaceLowest = Color(0xFF0d1017);
|
||||
static const Color darkSurfaceLow = Color(0xFF10131a);
|
||||
static const Color darkSurface = Color(0xFF151921);
|
||||
static const Color darkSurfaceHigh = Color(0xFF1a1f2a);
|
||||
static const Color darkSurfaceHighest = Color(0xFF22262f);
|
||||
|
||||
/// Ghost Border - 后备边框
|
||||
static const Color darkOutlineVariant = Color(0xFF45484f);
|
||||
|
||||
/// Primary - Neon Blue (主要交互)
|
||||
static const Color darkPrimary = Color(0xFF72dcff);
|
||||
static const Color darkPrimaryContainer = Color(0xFF4ac8f0);
|
||||
|
||||
/// Secondary - Electric Purple (次要强调)
|
||||
static const Color darkSecondary = Color(0xFFdd8bfb);
|
||||
static const Color darkSecondaryContainer = Color(0xFF2d1f3d);
|
||||
|
||||
/// Tertiary - Emerald Green (仅用于成功/盈利/买入)
|
||||
static const Color darkTertiary = Color(0xFFafffd1);
|
||||
static const Color darkTertiaryContainer = Color(0xFF1a3d2d);
|
||||
|
||||
/// 文本色
|
||||
static const Color darkOnSurface = Color(0xFFecedf6);
|
||||
static const Color darkOnSurfaceVariant = Color(0xFFa9abb3);
|
||||
static const Color darkOnSurfaceMuted = Color(0xFF6b6d75);
|
||||
|
||||
// ============================================
|
||||
// 语义色 - 涨跌 (明暗通用)
|
||||
// 浅色主题 - "The Ethereal Terminal"
|
||||
// ============================================
|
||||
|
||||
/// 涨/买入 - 绿色
|
||||
static const Color up = Color(0xFF00C853);
|
||||
/// 背景基色 - 珍珠白
|
||||
static const Color lightBackground = Color(0xFFf5f7f9);
|
||||
|
||||
/// 跌/卖出 - 红色
|
||||
/// Surface 层次 (从低到高)
|
||||
static const Color lightSurfaceLowest = Color(0xFFffffff); // Elevated (pop)
|
||||
static const Color lightSurfaceLow = Color(0xFFeef1f3); // Softly recessed
|
||||
static const Color lightSurface = Color(0xFFf5f7f9); // Base canvas
|
||||
static const Color lightSurfaceHigh = Color(0xFFe8ebef); // Elevated
|
||||
static const Color lightSurfaceHighest = Color(0xFFd9dde0); // Navigation anchor
|
||||
|
||||
/// Ghost Border
|
||||
static const Color lightOutlineVariant = Color(0xFFc4c8cc);
|
||||
|
||||
/// Primary - Plasma (主要交互)
|
||||
static const Color lightPrimary = Color(0xFF0050d4);
|
||||
static const Color lightPrimaryContainer = Color(0xFF7b9cff);
|
||||
|
||||
/// Secondary - Pulse indicator
|
||||
static const Color lightSecondary = Color(0xFF8319da);
|
||||
static const Color lightSecondaryContainer = Color(0xFFe8d4fa);
|
||||
|
||||
/// Tertiary - Success (买入/盈利)
|
||||
static const Color lightTertiary = Color(0xFF00a878);
|
||||
static const Color lightTertiaryContainer = Color(0xFFd4f5e9);
|
||||
|
||||
/// 文本色
|
||||
static const Color lightOnSurface = Color(0xFF2c2f31);
|
||||
static const Color lightOnSurfaceVariant = Color(0xFF5a5d60);
|
||||
static const Color lightOnSurfaceMuted = Color(0xFF8a8d90);
|
||||
|
||||
// ============================================
|
||||
// 语义色 - 明暗通用
|
||||
// ============================================
|
||||
|
||||
/// 涨/买入/成功
|
||||
static const Color up = darkTertiary;
|
||||
static const Color success = darkTertiary;
|
||||
|
||||
/// 跌/卖出/错误
|
||||
static const Color down = Color(0xFFFF5252);
|
||||
|
||||
/// 成功
|
||||
static const Color success = Color(0xFF00C853);
|
||||
static const Color error = down;
|
||||
|
||||
/// 警告
|
||||
static const Color warning = Color(0xFFFF9800);
|
||||
|
||||
/// 错误
|
||||
static const Color error = Color(0xFFFF5252);
|
||||
|
||||
/// 信息
|
||||
static const Color info = Color(0xFF2196F3);
|
||||
|
||||
// ============================================
|
||||
// 深色主题颜色 (Vercel/Linear 风格)
|
||||
// 渐变预设
|
||||
// ============================================
|
||||
|
||||
static const Color _darkBackground = Color(0xFF0A0A0B);
|
||||
static const Color _darkCardBackground = Color(0xFF111113);
|
||||
static const Color _darkSecondary = Color(0xFF1C1C1F);
|
||||
static const Color _darkMuted = Color(0xFF27272A);
|
||||
static const Color _darkBorder = Color(0xFF27272A);
|
||||
static const Color _darkTextPrimary = Color(0xFFFFFFFF);
|
||||
static const Color _darkTextSecondary = Color(0xFFA1A1AA);
|
||||
static const Color _darkTextHint = Color(0xFF71717A);
|
||||
/// 深色 CTA 渐变 (primary → primary_container, 135度)
|
||||
static const LinearGradient darkCtaGradient = LinearGradient(
|
||||
colors: [darkPrimary, darkPrimaryContainer],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// 浅色主题颜色
|
||||
// ============================================
|
||||
/// 浅色 CTA 渐变 (Plasma)
|
||||
static const LinearGradient lightCtaGradient = LinearGradient(
|
||||
colors: [lightPrimary, lightPrimaryContainer],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
|
||||
static const Color _lightBackground = Color(0xFFFFFFFF);
|
||||
static const Color _lightCardBackground = Color(0xFFFAFAFA);
|
||||
static const Color _lightSecondary = Color(0xFFF4F4F5);
|
||||
static const Color _lightMuted = Color(0xFFE4E4E7);
|
||||
static const Color _lightBorder = Color(0xFFE4E4E7);
|
||||
static const Color _lightTextPrimary = Color(0xFF0A0A0B);
|
||||
static const Color _lightTextSecondary = Color(0xFF52525B);
|
||||
static const Color _lightTextHint = Color(0xFF71717A);
|
||||
/// 兼容别名
|
||||
static const LinearGradient ctaGradient = darkCtaGradient;
|
||||
|
||||
/// 买入按钮渐变
|
||||
static const LinearGradient buyGradient = LinearGradient(
|
||||
colors: [darkTertiary, Color(0xFF7de8b8)],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
|
||||
/// 卖出按钮渐变
|
||||
static const LinearGradient sellGradient = LinearGradient(
|
||||
colors: [down, Color(0xFFe84545)],
|
||||
begin: Alignment(-0.7, -0.7),
|
||||
end: Alignment(0.7, 0.7),
|
||||
);
|
||||
|
||||
/// 资产卡片渐变
|
||||
static const LinearGradient assetCardGradient = LinearGradient(
|
||||
colors: [darkPrimary, darkSecondary],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Shadcn ColorScheme - 深色主题
|
||||
// ============================================
|
||||
|
||||
static ShadColorScheme get darkShad => ShadColorScheme(
|
||||
// 背景与前景
|
||||
background: _darkBackground,
|
||||
foreground: _darkTextPrimary,
|
||||
|
||||
// 卡片
|
||||
card: _darkCardBackground,
|
||||
cardForeground: _darkTextPrimary,
|
||||
|
||||
// 弹出层
|
||||
popover: _darkCardBackground,
|
||||
popoverForeground: _darkTextPrimary,
|
||||
|
||||
// 主色
|
||||
primary: primaryDark,
|
||||
primaryForeground: _darkTextPrimary,
|
||||
|
||||
// 次要色
|
||||
secondary: _darkSecondary,
|
||||
secondaryForeground: _darkTextPrimary,
|
||||
|
||||
// 静音色
|
||||
muted: _darkMuted,
|
||||
mutedForeground: _darkTextSecondary,
|
||||
|
||||
// 强调色
|
||||
accent: primaryDark.withValues(alpha: 0.15),
|
||||
accentForeground: primaryDark,
|
||||
|
||||
// 危险色
|
||||
background: darkBackground,
|
||||
foreground: darkOnSurface,
|
||||
card: darkSurface,
|
||||
cardForeground: darkOnSurface,
|
||||
popover: darkSurfaceHigh,
|
||||
popoverForeground: darkOnSurface,
|
||||
primary: darkPrimary,
|
||||
primaryForeground: darkBackground,
|
||||
secondary: darkSecondary,
|
||||
secondaryForeground: darkOnSurface,
|
||||
muted: darkSurfaceHigh,
|
||||
mutedForeground: darkOnSurfaceVariant,
|
||||
accent: darkPrimary.withValues(alpha: 0.15),
|
||||
accentForeground: darkPrimary,
|
||||
destructive: error,
|
||||
destructiveForeground: _darkTextPrimary,
|
||||
|
||||
// 边框与输入
|
||||
border: _darkBorder,
|
||||
input: _darkBorder,
|
||||
ring: primaryDark,
|
||||
|
||||
// 选择色
|
||||
selection: primaryDark.withValues(alpha: 0.3),
|
||||
destructiveForeground: darkOnSurface,
|
||||
border: darkOutlineVariant.withValues(alpha: 0.15),
|
||||
input: darkOutlineVariant.withValues(alpha: 0.15),
|
||||
ring: darkPrimary,
|
||||
selection: darkPrimary.withValues(alpha: 0.3),
|
||||
);
|
||||
|
||||
// ============================================
|
||||
@@ -120,45 +173,26 @@ class AppColorScheme {
|
||||
// ============================================
|
||||
|
||||
static ShadColorScheme get lightShad => ShadColorScheme(
|
||||
// 背景与前景
|
||||
background: _lightBackground,
|
||||
foreground: _lightTextPrimary,
|
||||
|
||||
// 卡片
|
||||
card: _lightCardBackground,
|
||||
cardForeground: _lightTextPrimary,
|
||||
|
||||
// 弹出层
|
||||
popover: _lightBackground,
|
||||
popoverForeground: _lightTextPrimary,
|
||||
|
||||
// 主色
|
||||
primary: primaryLight,
|
||||
primaryForeground: _lightBackground,
|
||||
|
||||
// 次要色
|
||||
secondary: _lightSecondary,
|
||||
secondaryForeground: _lightTextPrimary,
|
||||
|
||||
// 静音色
|
||||
muted: _lightMuted,
|
||||
mutedForeground: _lightTextSecondary,
|
||||
|
||||
// 强调色
|
||||
accent: primaryLight.withValues(alpha: 0.15),
|
||||
accentForeground: primaryLight,
|
||||
|
||||
// 危险色
|
||||
background: lightBackground,
|
||||
foreground: lightOnSurface,
|
||||
card: lightSurfaceLowest,
|
||||
cardForeground: lightOnSurface,
|
||||
popover: lightSurfaceLowest,
|
||||
popoverForeground: lightOnSurface,
|
||||
primary: lightPrimary,
|
||||
primaryForeground: Color(0xFFFFFFFF),
|
||||
secondary: lightSecondary,
|
||||
secondaryForeground: lightOnSurface,
|
||||
muted: lightSurfaceHigh,
|
||||
mutedForeground: lightOnSurfaceVariant,
|
||||
accent: lightPrimary.withValues(alpha: 0.1),
|
||||
accentForeground: lightPrimary,
|
||||
destructive: error,
|
||||
destructiveForeground: _lightBackground,
|
||||
|
||||
// 边框与输入
|
||||
border: _lightBorder,
|
||||
input: _lightBorder,
|
||||
ring: primaryLight,
|
||||
|
||||
// 选择色
|
||||
selection: primaryLight.withValues(alpha: 0.3),
|
||||
destructiveForeground: Color(0xFFFFFFFF),
|
||||
border: lightOutlineVariant.withValues(alpha: 0.5),
|
||||
input: lightOutlineVariant.withValues(alpha: 0.3),
|
||||
ring: lightPrimary,
|
||||
selection: lightPrimary.withValues(alpha: 0.2),
|
||||
);
|
||||
|
||||
// ============================================
|
||||
@@ -166,15 +200,22 @@ class AppColorScheme {
|
||||
// ============================================
|
||||
|
||||
static ColorScheme get darkMaterial => ColorScheme.dark(
|
||||
primary: primaryDark,
|
||||
onPrimary: _darkTextPrimary,
|
||||
secondary: _darkSecondary,
|
||||
onSecondary: _darkTextPrimary,
|
||||
primary: darkPrimary,
|
||||
onPrimary: darkBackground,
|
||||
secondary: darkSecondary,
|
||||
onSecondary: darkOnSurface,
|
||||
tertiary: darkTertiary,
|
||||
onTertiary: darkBackground,
|
||||
error: error,
|
||||
onError: _darkTextPrimary,
|
||||
surface: _darkCardBackground,
|
||||
onSurface: _darkTextPrimary,
|
||||
surfaceContainerHighest: _darkBackground,
|
||||
onError: darkOnSurface,
|
||||
surface: darkSurface,
|
||||
onSurface: darkOnSurface,
|
||||
surfaceContainerLowest: darkSurfaceLowest,
|
||||
surfaceContainerLow: darkSurfaceLow,
|
||||
surfaceContainer: darkSurface,
|
||||
surfaceContainerHigh: darkSurfaceHigh,
|
||||
surfaceContainerHighest: darkSurfaceHighest,
|
||||
outlineVariant: darkOutlineVariant,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
@@ -182,16 +223,81 @@ class AppColorScheme {
|
||||
// ============================================
|
||||
|
||||
static ColorScheme get lightMaterial => ColorScheme.light(
|
||||
primary: primaryLight,
|
||||
onPrimary: _lightBackground,
|
||||
secondary: _lightSecondary,
|
||||
onSecondary: _lightTextPrimary,
|
||||
primary: lightPrimary,
|
||||
onPrimary: Color(0xFFFFFFFF),
|
||||
secondary: lightSecondary,
|
||||
onSecondary: Color(0xFFFFFFFF),
|
||||
tertiary: lightTertiary,
|
||||
onTertiary: Color(0xFFFFFFFF),
|
||||
error: error,
|
||||
onError: _lightBackground,
|
||||
surface: _lightCardBackground,
|
||||
onSurface: _lightTextPrimary,
|
||||
surfaceContainerHighest: _lightBackground,
|
||||
onError: Color(0xFFFFFFFF),
|
||||
surface: lightSurface,
|
||||
onSurface: lightOnSurface,
|
||||
surfaceContainerLowest: lightSurfaceLowest,
|
||||
surfaceContainerLow: lightSurfaceLow,
|
||||
surfaceContainer: lightSurface,
|
||||
surfaceContainerHigh: lightSurfaceHigh,
|
||||
surfaceContainerHighest: lightSurfaceHighest,
|
||||
outlineVariant: lightOutlineVariant,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// 兼容性常量 (已废弃,保留向后兼容)
|
||||
// ============================================
|
||||
|
||||
@Deprecated('Use darkPrimary instead')
|
||||
static const Color primaryDark = darkPrimary;
|
||||
|
||||
@Deprecated('Use lightPrimary instead')
|
||||
static const Color primaryLight = lightPrimary;
|
||||
|
||||
@Deprecated('Use darkBackground instead')
|
||||
static const Color _darkBackground = darkBackground;
|
||||
|
||||
@Deprecated('Use darkSurface instead')
|
||||
static const Color _darkCardBackground = darkSurface;
|
||||
|
||||
@Deprecated('Use darkSurfaceHigh instead')
|
||||
static const Color _darkSecondary = darkSurfaceHigh;
|
||||
|
||||
@Deprecated('Use darkSurfaceHigh instead')
|
||||
static const Color _darkMuted = darkSurfaceHigh;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/// 创建 Shadcn 深色主题
|
||||
|
||||
@@ -67,7 +67,13 @@ class AppSpacing {
|
||||
static SizedBox vertical(double spacing) => SizedBox(height: spacing);
|
||||
}
|
||||
|
||||
/// 圆角系统 - 基于 modernization-v2.md 规范
|
||||
/// 圆角系统 - "The Kinetic Vault" & "The Ethereal Terminal" 规范
|
||||
///
|
||||
/// 设计规则:
|
||||
/// - 按钮: xxl (24px / 1.5rem)
|
||||
/// - 卡片: xl (16px)
|
||||
/// - 输入框: md (8px)
|
||||
/// - 标签: sm (4px)
|
||||
class AppRadius {
|
||||
AppRadius._();
|
||||
|
||||
@@ -78,19 +84,19 @@ class AppRadius {
|
||||
/// 小圆角 - 4px (标签、小组件)
|
||||
static const double sm = 4.0;
|
||||
|
||||
/// 中圆角 - 8px (按钮、输入框)
|
||||
/// 中圆角 - 8px (输入框)
|
||||
static const double md = 8.0;
|
||||
|
||||
/// 大圆角 - 12px (卡片)
|
||||
/// 大圆角 - 12px (卡片 - 旧版)
|
||||
static const double lg = 12.0;
|
||||
|
||||
/// 特大圆角 - 16px (大卡片)
|
||||
static const double xl = 16.0;
|
||||
|
||||
/// 超大圆角 - 24px (模态框、底部抽屉)
|
||||
/// 超大圆角 - 24px (按钮、模态框、底部抽屉)
|
||||
static const double xxl = 24.0;
|
||||
|
||||
/// 圆形 - 9999px
|
||||
/// 圆形 - 9999px (Pill buttons)
|
||||
static const double full = 9999.0;
|
||||
|
||||
// ============================================
|
||||
|
||||
@@ -3,294 +3,429 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'app_color_scheme.dart';
|
||||
import 'app_spacing.dart';
|
||||
|
||||
/// 应用主题配置 - 基于 modernization-v2.md 规范
|
||||
/// "The Kinetic Vault" & "The Ethereal Terminal" 主题配置
|
||||
class AppTheme {
|
||||
AppTheme._();
|
||||
|
||||
// ============================================
|
||||
// 深色主题
|
||||
// 深色主题 - "The Kinetic Vault"
|
||||
// ============================================
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: AppColorScheme.darkShad.background,
|
||||
primaryColor: AppColorScheme.primaryDark,
|
||||
scaffoldBackgroundColor: AppColorScheme.darkBackground,
|
||||
primaryColor: AppColorScheme.darkPrimary,
|
||||
colorScheme: AppColorScheme.darkMaterial,
|
||||
|
||||
// AppBar - 无边框规则
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: AppColorScheme.darkShad.background,
|
||||
foregroundColor: AppColorScheme.darkShad.foreground,
|
||||
backgroundColor: AppColorScheme.darkBackground,
|
||||
foregroundColor: AppColorScheme.darkOnSurface,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
titleTextStyle: GoogleFonts.inter(
|
||||
titleTextStyle: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColorScheme.darkShad.foreground,
|
||||
color: AppColorScheme.darkOnSurface,
|
||||
),
|
||||
),
|
||||
|
||||
// 卡片 - 无边框,使用 surface 层次
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColorScheme.darkSurface,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
),
|
||||
),
|
||||
|
||||
// 输入框 - Ghost Border 风格
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: AppColorScheme.darkShad.card,
|
||||
fillColor: AppColorScheme.darkSurfaceLow,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderSide: BorderSide(color: AppColorScheme.darkShad.border),
|
||||
borderSide: BorderSide(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderSide: BorderSide(color: AppColorScheme.darkShad.border),
|
||||
borderSide: BorderSide(
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderSide: BorderSide(color: AppColorScheme.primaryDark, width: 2),
|
||||
borderSide: const BorderSide(color: AppColorScheme.darkPrimary, width: 2),
|
||||
),
|
||||
hintStyle: TextStyle(color: AppColorScheme.darkShad.mutedForeground),
|
||||
hintStyle: TextStyle(color: AppColorScheme.darkOnSurfaceMuted),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
|
||||
// 按钮 - 渐变 CTA,大圆角
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColorScheme.primaryDark,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColorScheme.darkPrimary,
|
||||
foregroundColor: AppColorScheme.darkBackground,
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
),
|
||||
textStyle: GoogleFonts.inter(
|
||||
elevation: 0,
|
||||
textStyle: GoogleFonts.manrope(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColorScheme.primaryDark,
|
||||
foregroundColor: AppColorScheme.darkPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
// 分割线 - 使用层次而非边框
|
||||
dividerTheme: DividerThemeData(
|
||||
color: AppColorScheme.darkShad.border,
|
||||
color: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.1),
|
||||
thickness: 1,
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColorScheme.darkShad.card,
|
||||
|
||||
// 底部导航
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
backgroundColor: AppColorScheme.darkSurface,
|
||||
selectedItemColor: AppColorScheme.darkPrimary,
|
||||
unselectedItemColor: AppColorScheme.darkOnSurfaceVariant,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 浅色主题
|
||||
// 浅色主题 - "The Ethereal Terminal"
|
||||
// ============================================
|
||||
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: AppColorScheme.lightShad.background,
|
||||
primaryColor: AppColorScheme.primaryLight,
|
||||
scaffoldBackgroundColor: AppColorScheme.lightBackground,
|
||||
primaryColor: AppColorScheme.lightPrimary,
|
||||
colorScheme: AppColorScheme.lightMaterial,
|
||||
|
||||
// AppBar - 珍珠白
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: AppColorScheme.lightShad.background,
|
||||
foregroundColor: AppColorScheme.lightShad.foreground,
|
||||
backgroundColor: AppColorScheme.lightBackground,
|
||||
foregroundColor: AppColorScheme.lightOnSurface,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
titleTextStyle: GoogleFonts.inter(
|
||||
titleTextStyle: GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColorScheme.lightShad.foreground,
|
||||
color: AppColorScheme.lightOnSurface,
|
||||
),
|
||||
),
|
||||
|
||||
// 卡片 - Elevated (pop) on pearl background
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColorScheme.lightSurfaceLowest,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
),
|
||||
),
|
||||
|
||||
// 输入框 - 底部线条风格
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: AppColorScheme.lightShad.card,
|
||||
border: OutlineInputBorder(
|
||||
fillColor: AppColorScheme.lightSurfaceLow,
|
||||
border: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderSide: BorderSide(color: AppColorScheme.lightShad.border),
|
||||
borderSide: BorderSide(
|
||||
color: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderSide: BorderSide(color: AppColorScheme.lightShad.border),
|
||||
borderSide: BorderSide(
|
||||
color: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5),
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderSide: BorderSide(color: AppColorScheme.primaryLight, width: 2),
|
||||
borderSide: const BorderSide(color: AppColorScheme.lightPrimary, width: 2),
|
||||
),
|
||||
hintStyle: TextStyle(color: AppColorScheme.lightShad.mutedForeground),
|
||||
hintStyle: TextStyle(color: AppColorScheme.lightOnSurfaceMuted),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.md,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
|
||||
// 按钮 - Plasma 渐变,full 圆角
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColorScheme.primaryLight,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColorScheme.lightPrimary,
|
||||
foregroundColor: const Color(0xFFFFFFFF),
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
),
|
||||
textStyle: GoogleFonts.inter(
|
||||
elevation: 0,
|
||||
textStyle: GoogleFonts.manrope(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColorScheme.primaryLight,
|
||||
foregroundColor: AppColorScheme.lightPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
// 分割线 - 不使用,用留白代替
|
||||
dividerTheme: DividerThemeData(
|
||||
color: AppColorScheme.lightShad.border,
|
||||
color: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.2),
|
||||
thickness: 1,
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColorScheme.lightShad.card,
|
||||
|
||||
// 底部导航
|
||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
||||
backgroundColor: AppColorScheme.lightSurfaceHighest,
|
||||
selectedItemColor: AppColorScheme.lightPrimary,
|
||||
unselectedItemColor: AppColorScheme.lightOnSurfaceVariant,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 文本样式 - 使用 Google Fonts
|
||||
/// "The Kinetic Vault" & "The Ethereal Terminal" 文字样式系统
|
||||
///
|
||||
/// Display/Headlines: Space Grotesk (编辑风格)
|
||||
/// Body/Labels: Manrope (实用风格)
|
||||
class AppTextStyles {
|
||||
AppTextStyles._();
|
||||
|
||||
// ============================================
|
||||
// 标题样式 - Inter 字体
|
||||
// Display - 编辑风格 (Space Grotesk)
|
||||
// ============================================
|
||||
|
||||
static TextStyle displayLarge(BuildContext context) => GoogleFonts.inter(
|
||||
fontSize: 32,
|
||||
/// D1 - 大标题 (48px) - Hero moments
|
||||
static TextStyle displayLarge(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.1,
|
||||
letterSpacing: -0.02,
|
||||
);
|
||||
|
||||
static TextStyle displayMedium(BuildContext context) => GoogleFonts.inter(
|
||||
/// D2 - 中标题 (36px)
|
||||
static TextStyle displayMedium(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.2,
|
||||
letterSpacing: -0.01,
|
||||
);
|
||||
|
||||
/// D3 - 小标题 (28px)
|
||||
static TextStyle displaySmall(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.25,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// Headline - 编辑风格 (Space Grotesk)
|
||||
// ============================================
|
||||
|
||||
static TextStyle headlineLarge(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.3,
|
||||
);
|
||||
|
||||
static TextStyle displaySmall(BuildContext context) => GoogleFonts.inter(
|
||||
static TextStyle headlineMedium(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.35,
|
||||
);
|
||||
|
||||
static TextStyle headlineSmall(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// 正文样式 - Inter 字体
|
||||
// Body - 实用风格 (Manrope)
|
||||
// ============================================
|
||||
|
||||
static TextStyle bodyLarge(BuildContext context) => GoogleFonts.inter(
|
||||
static TextStyle bodyLarge(BuildContext context) => GoogleFonts.manrope(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.5,
|
||||
);
|
||||
|
||||
static TextStyle bodyMedium(BuildContext context) => GoogleFonts.inter(
|
||||
static TextStyle bodyMedium(BuildContext context) => GoogleFonts.manrope(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.5,
|
||||
);
|
||||
|
||||
static TextStyle bodySmall(BuildContext context) => GoogleFonts.inter(
|
||||
static TextStyle bodySmall(BuildContext context) => GoogleFonts.manrope(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// 数字样式 - JetBrains Mono 等宽字体
|
||||
// Label - 实用风格 (Manrope)
|
||||
// ============================================
|
||||
|
||||
static TextStyle numberLarge(BuildContext context) => GoogleFonts.jetBrainsMono(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
);
|
||||
|
||||
static TextStyle numberMedium(BuildContext context) => GoogleFonts.jetBrainsMono(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
);
|
||||
|
||||
static TextStyle numberSmall(BuildContext context) => GoogleFonts.jetBrainsMono(
|
||||
static TextStyle labelLarge(BuildContext context) => GoogleFonts.manrope(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
static TextStyle labelMedium(BuildContext context) => GoogleFonts.manrope(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
static TextStyle labelSmall(BuildContext context) => GoogleFonts.manrope(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// 便捷静态样式(兼容旧代码)
|
||||
// 数字/金额 - Space Grotesk (等宽特性)
|
||||
// ============================================
|
||||
|
||||
static TextStyle numberLarge(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.2,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
);
|
||||
|
||||
static TextStyle numberMedium(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.25,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
);
|
||||
|
||||
static TextStyle numberSmall(BuildContext context) => GoogleFonts.spaceGrotesk(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.3,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
);
|
||||
|
||||
// ============================================
|
||||
// 兼容旧代码的静态样式 (已废弃)
|
||||
// ============================================
|
||||
|
||||
@Deprecated('Use displaySmall instead')
|
||||
static const TextStyle heading1 = TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use headlineLarge instead')
|
||||
static const TextStyle heading2 = TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use headlineMedium instead')
|
||||
static const TextStyle heading3 = TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use headlineSmall instead')
|
||||
static const TextStyle heading4 = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use bodyLarge instead')
|
||||
static const TextStyle body1 = TextStyle(
|
||||
fontSize: 16,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use bodyMedium instead')
|
||||
static const TextStyle body2 = TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use labelSmall instead')
|
||||
static const TextStyle caption = TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFFA1A1AA),
|
||||
);
|
||||
|
||||
@Deprecated('Use labelSmall instead')
|
||||
static const TextStyle hint = TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFF71717A),
|
||||
);
|
||||
|
||||
@Deprecated('Use numberMedium instead')
|
||||
static const TextStyle price = TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
@Deprecated('Use numberSmall with color instead')
|
||||
static const TextStyle change = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
@Deprecated('Use numberSmall instead')
|
||||
static const TextStyle number = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import '../../core/theme/app_color_scheme.dart';
|
||||
import '../../core/theme/app_spacing.dart';
|
||||
|
||||
/// 资产卡片组件 - 用于显示资产总览
|
||||
///
|
||||
/// 设计规则 ("The Kinetic Vault"):
|
||||
/// - 渐变背景: Neon Blue → Electric Purple
|
||||
/// - 圆角: xl (16px)
|
||||
/// - 无边框,使用渐变层次
|
||||
/// - 微妙阴影: 10% black, blur 10px
|
||||
class AssetCard extends StatelessWidget {
|
||||
final String title;
|
||||
final String balance;
|
||||
@@ -13,16 +21,16 @@ class AssetCard extends StatelessWidget {
|
||||
final Gradient? gradient;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
// 默认渐变色 - 使用品牌青绿色
|
||||
/// 默认渐变色 - Neon Blue → Electric Purple
|
||||
static const defaultGradient = LinearGradient(
|
||||
colors: [AppColorScheme.primaryDark, Color(0xFF00B894)],
|
||||
colors: [AppColorScheme.darkPrimary, AppColorScheme.darkSecondary],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
// 深色渐变
|
||||
static const darkGradient = LinearGradient(
|
||||
colors: [Color(0xFF1E3A5F), Color(0xFF0D253F)],
|
||||
/// 翡翠渐变 - 用于盈利展示
|
||||
static const emeraldGradient = LinearGradient(
|
||||
colors: [AppColorScheme.darkTertiary, Color(0xFF7de8b8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
@@ -48,10 +56,10 @@ class AssetCard extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
gradient: cardGradient,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
@@ -79,18 +87,19 @@ class AssetCard extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// 余额
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
// 余额 - 大标题
|
||||
Text(
|
||||
balance,
|
||||
style: theme.textTheme.h1.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontSize: 32,
|
||||
),
|
||||
),
|
||||
// 盈亏信息
|
||||
if (profit != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
@@ -108,7 +117,7 @@ class AssetCard extends StatelessWidget {
|
||||
],
|
||||
// 子项
|
||||
if (items != null && items!.isNotEmpty) ...[
|
||||
const SizedBox(height: 20),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: items!.map((item) => _buildItem(item)).toList(),
|
||||
@@ -154,6 +163,11 @@ class AssetItem {
|
||||
}
|
||||
|
||||
/// 简洁资产卡片 - 用于列表中显示
|
||||
///
|
||||
/// 设计规则:
|
||||
/// - 使用 surface 层次而非边框
|
||||
/// - 圆角: xl (16px)
|
||||
/// - 涨跌标签: 15% 透明度背景
|
||||
class AssetCardCompact extends StatelessWidget {
|
||||
final String title;
|
||||
final String balance;
|
||||
@@ -176,7 +190,7 @@ class AssetCardCompact extends StatelessWidget {
|
||||
final isValueUp = isUp ?? true;
|
||||
|
||||
return ShadCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
@@ -204,7 +218,7 @@ class AssetCardCompact extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: getChangeBackgroundColor(isValueUp),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text(
|
||||
change!,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../../core/theme/app_color_scheme.dart';
|
||||
import '../../core/theme/app_spacing.dart';
|
||||
|
||||
/// 币种卡片组件 - 用于显示币种信息
|
||||
///
|
||||
/// 设计规则:
|
||||
/// - 使用 surface 层次而非边框
|
||||
/// - 圆角: xl (16px) 卡片, sm (4px) 涨跌标签
|
||||
/// - 涨跌标签: 15% 透明度背景
|
||||
class CoinCard extends StatelessWidget {
|
||||
final String code;
|
||||
final String name;
|
||||
@@ -28,12 +34,12 @@ class CoinCard extends StatelessWidget {
|
||||
final theme = ShadTheme.of(context);
|
||||
|
||||
return ShadCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
// 图标
|
||||
// 图标 - 圆形
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
@@ -52,7 +58,7 @@ class CoinCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
// 名称
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -71,12 +77,12 @@ class CoinCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
// 涨跌幅
|
||||
// 涨跌幅 - Dynamic Chip
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: getChangeBackgroundColor(isUp),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Text(
|
||||
change,
|
||||
|
||||
223
flutter_monisuo/lib/ui/components/gradient_button.dart
Normal file
223
flutter_monisuo/lib/ui/components/gradient_button.dart
Normal file
@@ -0,0 +1,223 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/theme/app_color_scheme.dart';
|
||||
import '../../core/theme/app_spacing.dart';
|
||||
|
||||
/// 渐变按钮组件 - 支持 CTA 渐变效果
|
||||
///
|
||||
/// 设计规则:
|
||||
/// - 渐变方向: 135度 (primary → primary_container)
|
||||
/// - 圆角: xxl (24px / 1.5rem)
|
||||
/// - 无边框
|
||||
class GradientButton extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isLoading;
|
||||
final bool isFullWidth;
|
||||
final LinearGradient? gradient;
|
||||
final IconData? icon;
|
||||
final double height;
|
||||
|
||||
const GradientButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.isLoading = false,
|
||||
this.isFullWidth = true,
|
||||
this.gradient,
|
||||
this.icon,
|
||||
this.height = 48,
|
||||
});
|
||||
|
||||
/// CTA 按钮 - 使用主题渐变
|
||||
factory GradientButton.cta({
|
||||
Key? key,
|
||||
required String text,
|
||||
VoidCallback? onPressed,
|
||||
bool isLoading = false,
|
||||
bool isFullWidth = true,
|
||||
IconData? icon,
|
||||
bool isDark = true,
|
||||
}) {
|
||||
return GradientButton(
|
||||
key: key,
|
||||
text: text,
|
||||
onPressed: onPressed,
|
||||
isLoading: isLoading,
|
||||
isFullWidth: isFullWidth,
|
||||
icon: icon,
|
||||
gradient: isDark ? AppColorScheme.darkCtaGradient : AppColorScheme.lightCtaGradient,
|
||||
);
|
||||
}
|
||||
|
||||
/// 买入按钮 - 翡翠绿渐变
|
||||
factory GradientButton.buy({
|
||||
Key? key,
|
||||
required String text,
|
||||
VoidCallback? onPressed,
|
||||
bool isLoading = false,
|
||||
bool isFullWidth = true,
|
||||
IconData? icon,
|
||||
}) {
|
||||
return GradientButton(
|
||||
key: key,
|
||||
text: text,
|
||||
onPressed: onPressed,
|
||||
isLoading: isLoading,
|
||||
isFullWidth: isFullWidth,
|
||||
icon: icon ?? Icons.arrow_downward_rounded,
|
||||
gradient: AppColorScheme.buyGradient,
|
||||
);
|
||||
}
|
||||
|
||||
/// 卖出按钮 - 红色渐变
|
||||
factory GradientButton.sell({
|
||||
Key? key,
|
||||
required String text,
|
||||
VoidCallback? onPressed,
|
||||
bool isLoading = false,
|
||||
bool isFullWidth = true,
|
||||
IconData? icon,
|
||||
}) {
|
||||
return GradientButton(
|
||||
key: key,
|
||||
text: text,
|
||||
onPressed: onPressed,
|
||||
isLoading: isLoading,
|
||||
isFullWidth: isFullWidth,
|
||||
icon: icon ?? Icons.arrow_upward_rounded,
|
||||
gradient: AppColorScheme.sellGradient,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final buttonGradient = gradient ?? AppColorScheme.darkCtaGradient;
|
||||
|
||||
return Container(
|
||||
width: isFullWidth ? double.infinity : null,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: buttonGradient,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: isLoading ? null : onPressed,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: isFullWidth ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (isLoading)
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 18, color: Colors.white),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ghost 按钮 - 次要操作
|
||||
///
|
||||
/// 设计规则:
|
||||
/// - Ghost Border: 15% opacity outline-variant
|
||||
/// - 圆角: xxl (24px)
|
||||
/// - 主色文字
|
||||
class GhostButton extends StatelessWidget {
|
||||
final String text;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isLoading;
|
||||
final bool isFullWidth;
|
||||
final IconData? icon;
|
||||
|
||||
const GhostButton({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.onPressed,
|
||||
this.isLoading = false,
|
||||
this.isFullWidth = true,
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final textColor = isDark ? AppColorScheme.darkPrimary : AppColorScheme.lightPrimary;
|
||||
final borderColor = isDark
|
||||
? AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15)
|
||||
: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.3);
|
||||
|
||||
return Container(
|
||||
width: isFullWidth ? double.infinity : null,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.all(color: borderColor, width: 1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: isLoading ? null : onPressed,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: isFullWidth ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (isLoading)
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: textColor,
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 18, color: textColor),
|
||||
SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import '../../core/theme/app_color_scheme.dart';
|
||||
import '../../core/theme/app_spacing.dart';
|
||||
|
||||
/// 交易按钮组件 - 买入/卖出按钮
|
||||
///
|
||||
/// 设计规则:
|
||||
/// - 渐变按钮: 135度渐变
|
||||
/// - 圆角: xxl (24px / 1.5rem)
|
||||
/// - 买入: 翡翠绿渐变
|
||||
/// - 卖出: 红色渐变
|
||||
class TradeButton extends StatelessWidget {
|
||||
final bool isBuy;
|
||||
final String? coinCode;
|
||||
@@ -39,44 +45,57 @@ class TradeButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = isBuy ? AppColorScheme.up : AppColorScheme.down;
|
||||
final text = isBuy ? '买入${coinCode != null ? ' $coinCode' : ''}' : '卖出${coinCode != null ? ' $coinCode' : ''}';
|
||||
final icon = isBuy ? LucideIcons.arrowDownToLine : LucideIcons.arrowUpFromLine;
|
||||
final gradient = isBuy ? AppColorScheme.buyGradient : AppColorScheme.sellGradient;
|
||||
final text = isBuy
|
||||
? '买入${coinCode != null ? ' $coinCode' : ''}'
|
||||
: '卖出${coinCode != null ? ' $coinCode' : ''}';
|
||||
|
||||
final button = ShadButton(
|
||||
backgroundColor: color,
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
child: Row(
|
||||
mainAxisSize: fullWidth ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (isLoading)
|
||||
const SizedBox.square(
|
||||
dimension: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(icon, size: 18, color: Colors.white),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
return Container(
|
||||
width: fullWidth ? double.infinity : null,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
gradient: gradient,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: isLoading ? null : onPressed,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xxl),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: fullWidth ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (isLoading)
|
||||
const SizedBox.square(
|
||||
dimension: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
else
|
||||
Icon(
|
||||
isBuy ? Icons.arrow_downward_rounded : Icons.arrow_upward_rounded,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (fullWidth) {
|
||||
return SizedBox(width: double.infinity, child: button);
|
||||
}
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +127,7 @@ class TradeButtonGroup extends StatelessWidget {
|
||||
isLoading: isBuyLoading,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: TradeButton.sell(
|
||||
coinCode: coinCode,
|
||||
|
||||
Reference in New Issue
Block a user