This commit is contained in:
2026-03-23 02:43:35 +08:00
parent a27ee426db
commit a8f9882e54
18 changed files with 1368 additions and 418 deletions

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import '../constants/app_colors.dart';
/// 自定义品牌颜色方案 - 深色主题
///
/// 基于品牌色 #2563EB (专业蓝) 定制
class AppShadColorScheme {
AppShadColorScheme._();
/// 深色主题颜色
static ShadColorScheme get dark => ShadColorScheme(
// 背景与前景
background: AppColors.background,
foreground: AppColors.textPrimary,
// 卡片
card: AppColors.cardBackground,
cardForeground: AppColors.textPrimary,
// 弹出层
popover: AppColors.surface,
popoverForeground: AppColors.textPrimary,
// 主色
primary: AppColors.primary,
primaryForeground: Colors.white,
// 次要色
secondary: const Color(0xFF252542),
secondaryForeground: AppColors.textPrimary,
// 静音色
muted: const Color(0xFF2A2A45),
mutedForeground: AppColors.textSecondary,
// 强调色
accent: AppColors.primary.withValues(alpha: 0.15),
accentForeground: AppColors.primary,
// 危险色
destructive: AppColors.error,
destructiveForeground: Colors.white,
// 边框与输入
border: AppColors.border,
input: AppColors.inputBorder,
ring: AppColors.primary,
// 选择色
selection: AppColors.primary.withValues(alpha: 0.3),
);
}
/// 创建自定义 ShadThemeData
ShadThemeData createAppShadTheme() {
return ShadThemeData(
brightness: Brightness.dark,
colorScheme: AppShadColorScheme.dark,
);
}

View File

@@ -0,0 +1,145 @@
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.withOpacity(0.15) : down.withOpacity(0.15);
/// 渐变色 (兼容旧代码) - 品牌蓝
static const List<Color> gradientColors = [Color(0xFF2563EB), Color(0xFF1D4ED8)];
}

View File

@@ -0,0 +1,179 @@
import 'package:flutter/material.dart';
/// 间距系统
///
/// 使用 4px 基础单位,确保一致性
class AppSpacing {
AppSpacing._();
// ============================================
// 基础间距 (Base Spacing)
// ============================================
/// 极小 - 4px
static const double xs = 4.0;
/// 小 - 8px
static const double sm = 8.0;
/// 中 - 16px (默认)
static const double md = 16.0;
/// 大 - 24px
static const double lg = 24.0;
/// 特大 - 32px
static const double xl = 32.0;
/// 超大 - 48px
static const double xxl = 48.0;
// ============================================
// 预设 EdgeInsets
// ============================================
/// 页面内边距
static const EdgeInsets pagePadding = EdgeInsets.all(md);
/// 卡片内边距
static const EdgeInsets cardPadding = EdgeInsets.all(md);
/// 列表项内边距
static const EdgeInsets listItemPadding = EdgeInsets.symmetric(
horizontal: md,
vertical: sm,
);
/// 按钮内边距
static const EdgeInsets buttonPadding = EdgeInsets.symmetric(
horizontal: lg,
vertical: 14,
);
/// 输入框内边距
static const EdgeInsets inputPadding = EdgeInsets.symmetric(
horizontal: md,
vertical: 14,
);
// ============================================
// 便捷方法
// ============================================
/// 获取水平间距
static SizedBox horizontal(double spacing) => SizedBox(width: spacing);
/// 获取垂直间距
static SizedBox vertical(double spacing) => SizedBox(height: spacing);
}
/// 圆角系统
class AppRadius {
AppRadius._();
// ============================================
// 基础圆角 (Base Radius)
// ============================================
/// 小圆角 - 6px (标签、小组件)
static const double sm = 6.0;
/// 中圆角 - 10px (按钮、输入框)
static const double md = 10.0;
/// 大圆角 - 14px (卡片)
static const double lg = 14.0;
/// 特大圆角 - 20px (大卡片、模态框)
static const double xl = 20.0;
/// 圆形 - 999px
static const double full = 999.0;
// ============================================
// 预设 BorderRadius
// ============================================
/// 小圆角
static BorderRadius get radiusSm => BorderRadius.circular(sm);
/// 中圆角
static BorderRadius get radiusMd => BorderRadius.circular(md);
/// 大圆角
static BorderRadius get radiusLg => BorderRadius.circular(lg);
/// 特大圆角
static BorderRadius get radiusXl => BorderRadius.circular(xl);
/// 圆形
static BorderRadius get radiusFull => BorderRadius.circular(full);
}
/// 响应式断点
class AppBreakpoints {
AppBreakpoints._();
/// 手机竖屏
static const double mobile = 360;
/// 手机横屏/小平板
static const double tablet = 768;
/// 平板/桌面
static const double desktop = 1024;
/// 检查是否为手机
static bool isMobile(BuildContext context) {
return MediaQuery.of(context).size.width < tablet;
}
/// 检查是否为平板
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= tablet && width < desktop;
}
/// 检查是否为桌面
static bool isDesktop(BuildContext context) {
return MediaQuery.of(context).size.width >= desktop;
}
/// 根据屏幕宽度获取响应式值
static T responsive<T>(
BuildContext context, {
required T mobile,
T? tablet,
T? desktop,
}) {
final width = MediaQuery.of(context).size.width;
if (width >= AppBreakpoints.desktop) {
return desktop ?? tablet ?? mobile;
} else if (width >= AppBreakpoints.tablet) {
return tablet ?? mobile;
}
return mobile;
}
}
/// 触摸目标尺寸
class AppTouchTarget {
AppTouchTarget._();
/// 最小触摸目标 - 44x44 (iOS HIG 标准)
static const double minSize = 44.0;
/// 确保触摸目标足够大
static Widget ensureMinSize({
required Widget child,
double minSize = AppTouchTarget.minSize,
}) {
return ConstrainedBox(
constraints: BoxConstraints(
minWidth: minSize,
minHeight: minSize,
),
child: child,
);
}
}

View File

@@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
/// 文字样式系统
///
/// 统一管理所有文字样式,确保一致性
/// 所有样式默认使用 textPrimary 颜色,保证对比度
class AppTextStyles {
AppTextStyles._();
// ============================================
// 标题样式 (Headings)
// ============================================
/// H1 - 页面大标题
static const TextStyle h1 = TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
height: 1.3,
);
/// H2 - 区块标题
static const TextStyle h2 = TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
height: 1.3,
);
/// H3 - 卡片标题
static const TextStyle h3 = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
height: 1.4,
);
/// H4 - 小标题
static const TextStyle h4 = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
height: 1.4,
);
// ============================================
// 正文样式 (Body)
// ============================================
/// Body1 - 主要正文
static const TextStyle body1 = TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
color: AppColors.textPrimary,
height: 1.5,
);
/// Body2 - 次要正文
static const TextStyle body2 = TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColors.textPrimary,
height: 1.5,
);
// ============================================
// 辅助文字 (Caption & Small)
// ============================================
/// Caption - 说明文字
static const TextStyle caption = TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: AppColors.textSecondary,
height: 1.4,
);
/// Small - 极小文字
static const TextStyle small = TextStyle(
fontSize: 11,
fontWeight: FontWeight.normal,
color: AppColors.textSecondary,
height: 1.3,
);
/// Hint - 提示文字
static const TextStyle hint = TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: AppColors.textHint,
height: 1.4,
);
// ============================================
// 特殊样式 (Special)
// ============================================
/// 金额/价格 - 大号数字
static const TextStyle amount = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
height: 1.2,
fontFeatures: [FontFeature.tabularFigures()],
);
/// 价格 - 标准数字
static const TextStyle price = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
height: 1.3,
fontFeatures: [FontFeature.tabularFigures()],
);
/// 价格变化 - 涨跌幅
static const TextStyle priceChange = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
height: 1.3,
fontFeatures: [FontFeature.tabularFigures()],
);
/// 按钮文字
static const TextStyle button = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
height: 1.2,
);
/// 链接文字
static const TextStyle link = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.textLink,
decoration: TextDecoration.underline,
height: 1.4,
);
// ============================================
// 可复制修改的样式方法
// ============================================
/// 获取带颜色的标题样式
static TextStyle headingWithColor(TextStyle style, Color color) {
return style.copyWith(color: color);
}
/// 获取带颜色的正文样式
static TextStyle bodyWithColor(TextStyle style, Color color) {
return style.copyWith(color: color);
}
/// 获取粗体样式
static TextStyle bold(TextStyle style) {
return style.copyWith(fontWeight: FontWeight.bold);
}
/// 获取半粗体样式
static TextStyle semiBold(TextStyle style) {
return style.copyWith(fontWeight: FontWeight.w600);
}
}
/// 兼容旧代码的别名
@Deprecated('Use AppTextStyles instead')
class AppText {
AppText._();
}

View File

@@ -104,6 +104,12 @@ class AppTextStyles {
color: AppColors.textPrimary,
);
static const TextStyle heading4 = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
);
static const TextStyle body1 = TextStyle(
fontSize: 16,
color: AppColors.textPrimary,
@@ -123,6 +129,26 @@ class AppTextStyles {
fontSize: 14,
color: AppColors.textHint,
);
/// 价格样式 - 用于显示价格、金额
static const TextStyle price = TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
);
/// 涨跌幅样式
static const TextStyle change = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
);
/// 数字样式 - 用于显示数量
static const TextStyle number = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.textPrimary,
);
}
/// 间距常量
@@ -135,6 +161,21 @@ class AppSpacing {
static const double lg = 24.0;
static const double xl = 32.0;
static const double xxl = 48.0;
/// 页面水平内边距
static const double pageHorizontal = 16.0;
/// 页面垂直内边距
static const double pageVertical = 16.0;
/// 卡片内边距
static const double cardPadding = 16.0;
/// 列表项间距
static const double listItemSpacing = 8.0;
/// 表单字段间距
static const double formFieldSpacing = 12.0;
}
/// 圆角常量
@@ -146,4 +187,77 @@ class AppRadius {
static const double lg = 16.0;
static const double xl = 24.0;
static const double full = 999.0;
/// 卡片圆角
static const double card = lg;
/// 按钮圆角
static const double button = md;
/// 输入框圆角
static const double input = md;
/// 标签圆角
static const double badge = sm;
}
/// 响应式断点
class AppBreakpoints {
AppBreakpoints._();
/// 手机
static const double mobile = 600;
/// 平板
static const double tablet = 900;
/// 桌面
static const double desktop = 1200;
/// 判断是否为手机
static bool isMobile(BuildContext context) =>
MediaQuery.of(context).size.width < mobile;
/// 判断是否为平板
static bool isTablet(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width >= mobile && width < tablet;
}
/// 判断是否为桌面
static bool isDesktop(BuildContext context) =>
MediaQuery.of(context).size.width >= tablet;
}
/// 响应式工具
class Responsive {
Responsive._();
/// 根据屏幕宽度返回响应式值
static T value<T>(
BuildContext context, {
required T mobile,
T? tablet,
T? desktop,
}) {
final width = MediaQuery.of(context).size.width;
if (width >= AppBreakpoints.tablet && desktop != null) {
return desktop;
}
if (width >= AppBreakpoints.mobile && tablet != null) {
return tablet;
}
return mobile;
}
/// 响应式字体大小
static double fontSize(BuildContext context, double base) {
return value(context, mobile: base, tablet: base * 1.1, desktop: base * 1.2);
}
/// 响应式间距
static double spacing(BuildContext context, double base) {
return value(context, mobile: base, tablet: base * 1.2, desktop: base * 1.5);
}
}