refactor(theme): 迁移主题感知颜色至 ThemeExtension

- 创建 AppThemeColors ThemeExtension 类,统一管理主题感知颜色(涨跌色、卡片背景、渐变等)
- 从 AppColorScheme 移除主题感知辅助函数,仅保留静态颜色常量
- 在 AppTheme 中注册 ThemeExtension,支持深色/浅色主题工厂
- 重构所有 UI 组件使用 context.appColors 访问主题颜色,替代硬编码的 AppColorScheme 方法调用
- 移除组件中重复的 isDark 判断逻辑,简化颜色获取方式
- 保持向后兼容性,所有现有功能不变
This commit is contained in:
2026-04-06 01:58:08 +08:00
parent 396668aa43
commit 7ed2435a4c
36 changed files with 658 additions and 810 deletions

View File

@@ -178,57 +178,14 @@ class AppColorScheme {
static const Color info = Color(0xFF2196F3);
// ============================================
// 主题感知辅助函数 - 根据明暗模式选择正确的颜色
// 主题感知辅助函数 - 已迁移至 AppThemeColors ThemeExtension
// 以下方法保留仅供 AppThemeColors 工厂内部使用
// ============================================
/// 获取涨跌颜色(主题感知)
///
/// 在深色模式下使用亮绿色 (#afffd1),在亮色模式下使用深绿色 (#00a878) 以确保对比度
static Color getUpColor(bool isDark) => isDark ? darkTertiary : lightTertiary;
/// 获取涨跌背景色(主题感知)
///
/// 带透明度的背景色,用于标签、徽章等
static Color getUpBackgroundColor(bool isDark, {double opacity = 0.15}) {
return isDark
? darkTertiary.withValues(alpha: opacity)
: lightTertiary.withValues(alpha: opacity);
}
/// 获取跌/卖出颜色(主题感知)
static Color getDownColor(bool isDark) => isDark ? darkError : lightError;
/// 获取跌/卖出背景色(主题感知)
static Color getDownBackgroundColor(bool isDark, {double opacity = 0.15}) {
return isDark
? darkError.withValues(alpha: opacity)
: lightError.withValues(alpha: opacity);
}
/// 交易按钮买入色 - 深绿色填充,确保白色文字可读
static const Color buyButtonFill = Color(0xFF059669);
/// 交易按钮卖出色 - 深红色填充,确保白色文字可读
static const Color sellButtonFill = Color(0xFFDC2626);
/// 获取买入按钮渐变(主题感知)
static LinearGradient getBuyGradient(bool isDark) => LinearGradient(
colors: isDark
? [darkTertiary, darkTertiaryContainer]
: [lightTertiary, lightTertiaryContainer],
begin: const Alignment(-0.7, -0.7),
end: const Alignment(0.7, 0.7),
);
/// 获取翡翠渐变(主题感知)- 用于盈利展示
static LinearGradient getEmeraldGradient(bool isDark) => LinearGradient(
colors: isDark
? [darkTertiary, const Color(0xFF7de8b8)]
: [lightTertiary, const Color(0xFF00c987)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
// ============================================
// 渐变预设
// ============================================
@@ -426,14 +383,6 @@ class AppColorScheme {
/// 获取买入按钮渐变(主题感知)- 用于盈利展示
/// 获取涨跌颜色(明暗通用)
static Color getChangeColor(bool isUp) => isUp ? AppColorScheme.up : AppColorScheme.down;
/// 获取涨跌背景色(带透明度)
static Color getChangeBackgroundColor(bool isUp, {double opacity = 0.15}) {
return isUp
? AppColorScheme.up.withValues(alpha: opacity)
: AppColorScheme.down.withValues(alpha: opacity);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'app_color_scheme.dart';
import 'app_spacing.dart';
import 'app_theme_extension.dart';
/// "The Kinetic Vault" & "The Ethereal Terminal" 主题配置
class AppTheme {
@@ -18,6 +19,9 @@ class AppTheme {
scaffoldBackgroundColor: AppColorScheme.darkBackground,
primaryColor: AppColorScheme.darkPrimary,
colorScheme: AppColorScheme.darkMaterial,
extensions: <ThemeExtension<dynamic>>[
AppThemeColors.dark(),
],
// AppBar - 无边框规则
appBarTheme: AppBarTheme(
@@ -121,6 +125,9 @@ class AppTheme {
scaffoldBackgroundColor: AppColorScheme.lightBackground,
primaryColor: AppColorScheme.lightPrimary,
colorScheme: AppColorScheme.lightMaterial,
extensions: <ThemeExtension<dynamic>>[
AppThemeColors.light(),
],
// AppBar - 珍珠白
appBarTheme: AppBarTheme(

View File

@@ -0,0 +1,220 @@
import 'package:flutter/material.dart';
import 'app_color_scheme.dart';
/// 自定义主题扩展 — 注册到 ThemeData.extensions 中
///
/// 存放 ColorScheme 标准槽位无法覆盖的语义色:
/// - 卡片背景语义色dark/light 使用不同 surface 层级)
/// - 涨跌金融色(含带透明度的背景色)
/// - 渐变预设
/// - 光效透明度等
@immutable
class AppThemeColors extends ThemeExtension<AppThemeColors> {
// ---- 卡片背景语义色 ----
/// 标准卡片背景dark: surfaceContainer, light: surfaceContainerHigh
final Color surfaceCard;
/// 高亮卡片背景dark: surfaceContainerHighest, light: surfaceContainerHigh
final Color surfaceCardHigh;
// ---- 文字语义 ----
/// 弱化/辅助文字
final Color onSurfaceMuted;
// ---- 涨跌金融色 ----
/// 涨/盈利/买入
final Color up;
final Color upBackground;
/// 跌/亏损/卖出
final Color down;
final Color downBackground;
// ---- 强调色 ----
/// 主强调色dark: secondary 金色, light: primary 蓝色)
final Color accentPrimary;
// ---- 光效 ----
/// 光晕透明度dark: 0.15, light: 0.08
final double glowOpacity;
// ---- 边框 ----
/// Ghost BorderoutlineVariant 带透明度)
final Color ghostBorder;
// ---- 渐变预设 ----
final LinearGradient ctaGradient;
final LinearGradient buyGradient;
final LinearGradient sellGradient;
final LinearGradient assetGradient;
final LinearGradient emeraldGradient;
const AppThemeColors({
required this.surfaceCard,
required this.surfaceCardHigh,
required this.onSurfaceMuted,
required this.up,
required this.upBackground,
required this.down,
required this.downBackground,
required this.accentPrimary,
required this.glowOpacity,
required this.ghostBorder,
required this.ctaGradient,
required this.buyGradient,
required this.sellGradient,
required this.assetGradient,
required this.emeraldGradient,
});
/// 深色主题工厂
factory AppThemeColors.dark() => AppThemeColors(
surfaceCard: AppColorScheme.darkSurfaceContainer,
surfaceCardHigh: AppColorScheme.darkSurfaceContainerHighest,
onSurfaceMuted: AppColorScheme.darkOnSurfaceMuted,
up: AppColorScheme.darkTertiary,
upBackground: AppColorScheme.darkTertiary.withValues(alpha: 0.15),
down: AppColorScheme.darkError,
downBackground: AppColorScheme.darkError.withValues(alpha: 0.15),
accentPrimary: AppColorScheme.darkSecondary,
glowOpacity: 0.15,
ghostBorder: AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15),
ctaGradient: AppColorScheme.darkCtaGradient,
buyGradient: AppColorScheme.buyGradient,
sellGradient: AppColorScheme.sellGradient,
assetGradient: AppColorScheme.assetCardGradient,
emeraldGradient: const LinearGradient(
colors: [AppColorScheme.darkTertiary, Color(0xFF7de8b8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
);
/// 浅色主题工厂
factory AppThemeColors.light() => AppThemeColors(
surfaceCard: AppColorScheme.lightSurfaceContainerHigh,
surfaceCardHigh: AppColorScheme.lightSurfaceContainerHigh,
onSurfaceMuted: AppColorScheme.lightOnSurfaceMuted,
up: AppColorScheme.lightTertiary,
upBackground: AppColorScheme.lightTertiary.withValues(alpha: 0.15),
down: AppColorScheme.lightError,
downBackground: AppColorScheme.lightError.withValues(alpha: 0.15),
accentPrimary: AppColorScheme.lightPrimary,
glowOpacity: 0.08,
ghostBorder: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5),
ctaGradient: AppColorScheme.lightCtaGradient,
buyGradient: const LinearGradient(
colors: [AppColorScheme.lightTertiary, AppColorScheme.lightTertiaryContainer],
begin: Alignment(-0.7, -0.7),
end: Alignment(0.7, 0.7),
),
sellGradient: AppColorScheme.sellGradient,
assetGradient: const LinearGradient(
colors: [AppColorScheme.lightPrimary, AppColorScheme.lightSecondary],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
emeraldGradient: const LinearGradient(
colors: [AppColorScheme.lightTertiary, Color(0xFF00c987)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
);
@override
AppThemeColors copyWith({
Color? surfaceCard,
Color? surfaceCardHigh,
Color? onSurfaceMuted,
Color? up,
Color? upBackground,
Color? down,
Color? downBackground,
Color? accentPrimary,
double? glowOpacity,
Color? ghostBorder,
LinearGradient? ctaGradient,
LinearGradient? buyGradient,
LinearGradient? sellGradient,
LinearGradient? assetGradient,
LinearGradient? emeraldGradient,
}) {
return AppThemeColors(
surfaceCard: surfaceCard ?? this.surfaceCard,
surfaceCardHigh: surfaceCardHigh ?? this.surfaceCardHigh,
onSurfaceMuted: onSurfaceMuted ?? this.onSurfaceMuted,
up: up ?? this.up,
upBackground: upBackground ?? this.upBackground,
down: down ?? this.down,
downBackground: downBackground ?? this.downBackground,
accentPrimary: accentPrimary ?? this.accentPrimary,
glowOpacity: glowOpacity ?? this.glowOpacity,
ghostBorder: ghostBorder ?? this.ghostBorder,
ctaGradient: ctaGradient ?? this.ctaGradient,
buyGradient: buyGradient ?? this.buyGradient,
sellGradient: sellGradient ?? this.sellGradient,
assetGradient: assetGradient ?? this.assetGradient,
emeraldGradient: emeraldGradient ?? this.emeraldGradient,
);
}
@override
AppThemeColors lerp(AppThemeColors other, double t) {
if (t < 0.5) {
return copyWith(
surfaceCard: Color.lerp(surfaceCard, other.surfaceCard, t * 2),
surfaceCardHigh:
Color.lerp(surfaceCardHigh, other.surfaceCardHigh, t * 2),
onSurfaceMuted:
Color.lerp(onSurfaceMuted, other.onSurfaceMuted, t * 2),
up: Color.lerp(up, other.up, t * 2),
upBackground: Color.lerp(upBackground, other.upBackground, t * 2),
down: Color.lerp(down, other.down, t * 2),
downBackground: Color.lerp(downBackground, other.downBackground, t * 2),
accentPrimary:
Color.lerp(accentPrimary, other.accentPrimary, t * 2),
ghostBorder: Color.lerp(ghostBorder, other.ghostBorder, t * 2),
glowOpacity: lerpDouble(glowOpacity, other.glowOpacity, t * 2),
);
}
return other.copyWith(
surfaceCard: Color.lerp(other.surfaceCard, surfaceCard, (1 - t) * 2),
surfaceCardHigh: Color.lerp(
other.surfaceCardHigh, surfaceCardHigh, (1 - t) * 2),
onSurfaceMuted:
Color.lerp(other.onSurfaceMuted, onSurfaceMuted, (1 - t) * 2),
up: Color.lerp(other.up, up, (1 - t) * 2),
upBackground: Color.lerp(other.upBackground, upBackground, (1 - t) * 2),
down: Color.lerp(other.down, down, (1 - t) * 2),
downBackground:
Color.lerp(other.downBackground, downBackground, (1 - t) * 2),
accentPrimary:
Color.lerp(other.accentPrimary, accentPrimary, (1 - t) * 2),
ghostBorder: Color.lerp(other.ghostBorder, ghostBorder, (1 - t) * 2),
glowOpacity: lerpDouble(other.glowOpacity, glowOpacity, (1 - t) * 2),
);
}
static double lerpDouble(double a, double b, double t) => a + (b - a) * t;
}
/// BuildContext 主题快捷扩展
///
/// 用法:
/// context.colors.primary → Theme.of(context).colorScheme.primary
/// context.appColors.up → AppThemeColors 中的涨色
/// context.isDark → 是否深色模式
extension AppThemeContext on BuildContext {
/// Material ColorScheme 快捷访问
ColorScheme get colors => Theme.of(this).colorScheme;
/// 自定义语义色快捷访问
AppThemeColors get appColors =>
Theme.of(this).extension<AppThemeColors>()!;
/// 是否深色模式
bool get isDark => Theme.of(this).brightness == Brightness.dark;
/// TextTheme 快捷访问
TextTheme get textStyles => Theme.of(this).textTheme;
}