提示信息
# 同伴 App · 配色系统说明
> 说明配色体系的工作原理、如何切换主题色方案、如何新增颜色,以及渐变按钮的实现机制。
---
## 目录
1. [整体架构](#一整体架构)
2. [AppColors — 编辑风静态色板](#二appcolors--编辑风静态色板)
3. [AppAccentExtension — 配色方案注入](#三appaccentextension--配色方案注入)
4. [4 套内置配色方案](#四4-套内置配色方案)
5. [在组件中读取 accent 色](#五在组件中读取-accent-色)
6. [渐变按钮(PrimaryActionButton)](#六渐变按钮primaryactionbutton)
7. [深色/浅色/跟随系统](#七深色浅色跟随系统)
8. [界面圆角(AppShapeMode)](#八界面圆角appshapemode)
9. [如何新增颜色](#九如何新增颜色)
10. [如何新增配色方案](#十如何新增配色方案)
11. [操作决策树](#十一操作决策树)
---
## 一、整体架构
配色体系由以下三层构成:
| 层次 | 文件 | 说明 |
|------|------|------|
| 静态编辑风色板 | `theme/app_colors.dart` `AppColors` | 固定常量,不随方案变化 |
| 方案强调色 | `theme/app_colors.dart` `AppAccentExtension` | ThemeExtension,随 AppThemeScheme 切换 |
| 读取扩展 | `theme/app_colors.dart` `AppColorsExt` | BuildContext 语义色扩展 |
用户可在「页面与展示」(`/settings/display`)中实时切换方案,选择结果持久化到 MMKV,下次启动自动恢复。
---
## 二、AppColors — 编辑风静态色板
`theme/app_colors.dart` 定义所有与方案无关的固定色彩:
```dart
class AppColors {
// ── 浅色核心 ────────────────────────────────────────
static const Color paper = Color(0xFFF5F0E8); // 暖米纸主背景
static const Color warm = Color(0xFFFCFAF6); // 辅背景/卡片/输入框
static const Color card = Color(0xFFEDE8DF); // 嵌套卡片
static const Color ink = Color(0xFF1A1A1A); // 主文字/按钮背景
static const Color accent = Color(0xFFC84B2F); // 默认砖红(indigo 方案浅色)
static const Color muted = Color(0xFF9E9587); // 次要文字
// ── 深色核心 ─────────────────────────────────────────
static const Color darkVoid = Color(0xFF0D0D1A);
static const Color darkAccent = Color(0xFFF0C060); // 默认金色(indigo 方案深色)
// ...
// ── 语义色(固定,不随方案变化)─────────────────────
static const Color warning = Color(0xFFF59E0B);
static const Color success = Color(0xFF4B7A47);
static const Gradient brandGradient = LinearGradient(
colors: [Color(0xFFC84B2F), Color(0xFFF0C060)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
}
```
> **强制规则**:组件内禁止使用 `Colors.xxx` 字面量。
> 动态色(深浅自适应)必须通过 `context.themeXxx` 扩展获取。
---
## 三、AppAccentExtension — 配色方案注入
`AppAccentExtension` 是一个 `ThemeExtension`,通过 `ThemeData.extensions` 注入到全局主题,让所有组件能通过 `context.themeAccent` 获取当前方案的强调色。
```dart
// theme/app_colors.dart
class AppAccentExtension extends ThemeExtension<AppAccentExtension> {
final AppThemeScheme scheme;
final bool isDark;
const AppAccentExtension({
this.scheme = AppThemeScheme.indigo,
this.isDark = false,
});
// 当前强调色(随深浅模式自动切换)
Color get accent => isDark ? _darkAccent : _lightAccent;
// 当前渐变色列表(用于主按钮)
List<Color> get gradient => isDark ? _darkGradient : _lightGradient;
// 静态工具方法
static Color previewColor(AppThemeScheme scheme) => ... // 浅色 accent,用于设置页色块
static String label(AppThemeScheme scheme) => ... // 方案显示名称
}
```
`app_theme.dart` 在构建主题时注入:
```dart
static ThemeData light({
AppShapeMode shape = AppShapeMode.rounded,
AppThemeScheme scheme = AppThemeScheme.indigo,
}) => _createTheme(Brightness.light, shape, scheme);
static ThemeData _createTheme(Brightness brightness, AppShapeMode shape, AppThemeScheme scheme) {
final shapeExt = AppShapeExtension(mode: shape);
final accentExt = AppAccentExtension(scheme: scheme, isDark: brightness == Brightness.dark);
return ThemeData(
extensions: [shapeExt, accentExt],
...
);
}
```
`app.dart` 监听两个 Provider 并传入:
```dart
final shapeMode = ref.watch(appShapeModeProvider);
final themeScheme = ref.watch(themeSchemeProvider);
MaterialApp.router(
theme: AppTheme.light(shape: shapeMode, scheme: themeScheme),
darkTheme: AppTheme.dark(shape: shapeMode, scheme: themeScheme),
themeMode: ref.watch(themeModeProvider),
...
)
```
---
## 四、4 套内置配色方案
配色方案定义在 `AppAccentExtension` 的 switch 表达式中(所有值均在 `app_colors.dart`):
| 枚举值 | 显示名 | 浅色 accent | 深色 accent | 浅色渐变 |
|--------|--------|------------|------------|---------|
| `indigo` | 砖红 | `#C84B2F` 砖红 | `#F0C060` 金 | 砖红 → 暖橙 |
| `rose` | 玫红 | `#C45060` 尘玫 | `#E88890` 浅玫 | 尘玫 → 玫粉 |
| `emerald` | 山水绿 | `#3A6B54` 墨绿 | `#72B895` 玉绿 | 墨绿 → 苔绿 |
| `violet` | 幽紫 | `#5E3D7C` 深紫 | `#B497CC` 淡紫 | 深紫 → 蓝紫 |
---
## 五、在组件中读取 accent 色
组件通过 `BuildContext` 扩展方法获取(定义在 `AppColorsExt`):
```dart
// theme/app_colors.dart → AppColorsExt extension on BuildContext
// ✅ 强调色(随方案 + 深浅模式变化)
context.themeAccent // 当前方案 accent
// ✅ 主按钮渐变(随方案 + 深浅模式变化)
context.themeAccentGradient // List<Color>,用于 linearGradient
// ✅ 错误色(与 accent 保持一致)
context.themeError
// ── 不随方案变化的颜色 ─────────────────────
context.themeTextPrimary // 主文字
context.themeTextSecondary // 次要文字
context.themeBackground // 页面背景
context.themeSurface // 卡片/模块背景
context.themeDivider // 分割线
```
**禁止** 直接访问 `ThemeExtension`:
```dart
// ❌ 禁止(绕过设计系统)
Theme.of(context).extension<AppAccentExtension>()?.accent
// ✅ 正确
context.themeAccent
```
---
## 六、主操作按钮(PrimaryActionButton)
`PrimaryActionButton` 是唯一被允许使用 `themeAccent` 的组件,背景色 = `themeAccent`(当前为极简模式 `ink`)。
### 参数与状态
```dart
// 正常状态
PrimaryActionButton(text: '确认', onTap: () async { ... })
// Loading 状态(两种方式)
// 方式 A:外部显式控制
PrimaryActionButton(text: '保存', isLoading: true, onTap: ...)
// 方式 B:内部自动管理(onTap 为 async,执行期间自动显示 spinner)
PrimaryActionButton(text: '保存', onTap: () async {
await someAsyncOperation(); // 执行期间按钮自动 loading
})
// Disabled 状态(条件不满足时禁用)
PrimaryActionButton(text: '登录', enabled: false, onTap: ...)
// 危险操作(error 色背景)
PrimaryActionButton(text: '删除', isDestructive: true, onTap: ...)
// 次要操作(灰色背景)
PrimaryActionButton(text: '取消', isSecondary: true, onTap: ...)
```
### 状态表
| 状态 | 触发方式 | 外观 | 点击 |
|------|---------|------|------|
| 正常 | 默认 | 全不透明,有阴影 | 可点击 |
| Loading | `isLoading:true` 或 async 执行中 | opacity 0.45 + spinner | 禁止 |
| Disabled | `enabled:false` | opacity 0.45,无阴影 | 禁止 |
| Destructive | `isDestructive:true` | `themeError` 背景 | 可点击 |
| Secondary | `isSecondary:true` | `themeSurfaceVariant` 背景 | 可点击 |
### 规范
- 高度 `AppSpacing.actionHeight`(56dp),圆角随全局 `AppShapeMode` 变化
- Loading spinner 颜色 = `contentColor`(随深浅/Secondary 自动切换,不硬编码)
- Secondary loading spinner 使用 `themeTextPrimary` 颜色
- 禁止用 `ElevatedButton` 替代
---
## 七、深色/浅色/跟随系统
主题模式由 `themeModeProvider` 管理(`providers/display_provider.dart`):
```dart
// 读取
final mode = ref.watch(themeModeProvider);
// 切换(持久化到 MMKV)
ref.read(themeModeProvider.notifier).set(ThemeMode.dark);
AppStorage.setInt(StorageKeys.themeMode, ThemeMode.dark.index);
```
组件内判断当前深浅模式:
```dart
context.isDark // bool,来自 AppColorsExt
```
---
## 八、界面圆角(AppShapeMode)
全局圆角模式由 `appShapeModeProvider` 管理,通过 `AppShapeExtension` 注入主题:
```dart
// 三种模式
enum AppShapeMode {
rounded, // 标准圆角(默认):按钮12dp / 输入框12dp / 标签8dp / 卡片16dp
square, // 方直:所有角 4dp
pill, // 胶囊:按钮/标签 全圆 / 输入框12dp / 卡片16dp
}
// 切换
ref.read(appShapeModeProvider.notifier).set(AppShapeMode.pill);
```
组件内读取:
```dart
context.appButtonRadius // BorderRadius
context.appInputRadius // BorderRadius
context.appTagRadius // BorderRadius
context.appCardRadius // BorderRadius
context.appActionSheetRadius
```
---
## 九、如何新增颜色
### 场景 A:固定语义色(不随方案/深浅变化)
直接在 `AppColors` 追加静态常量:
```dart
class AppColors {
// 追加新颜色
static const Color vipGold = Color(0xFFFFB800);
static const Color onlineGreen = Color(0xFF4B7A47);
}
```
组件直接引用:
```dart
AppColors.vipGold
```
### 场景 B:随深浅模式变化的颜色
在 `AppColorsExt` 追加扩展方法:
```dart
extension AppColorsExt on BuildContext {
// 追加
Color get themeVipBg => isDark
? const Color(0xFF2A2010) // 深色模式
: const Color(0xFFFFF8E1); // 浅色模式
}
```
组件引用:
```dart
context.themeVipBg
```
### 场景 C:随配色方案变化的颜色
若是**强调色家族**(如按钮、链接、选中态),在 `AppAccentExtension` 对应的 switch 里添加新值即可(通常已经通过 `context.themeAccent` 覆盖了这些场景)。
若是独立的方案色,新建一个 `ThemeExtension`,参考 `AppAccentExtension` 的模式。
---
## 十、如何新增配色方案
### 步骤一:在枚举中添加新方案
`theme/app_colors.dart`:
```dart
enum AppThemeScheme {
indigo,
rose,
emerald,
violet,
amber, // ← 新增
}
```
### 步骤二:在 AppAccentExtension 中添加颜色
`theme/app_colors.dart` → `AppAccentExtension`,在所有 switch 表达式中各加一条:
```dart
Color get _lightAccent => switch (scheme) {
// ... 现有方案 ...
AppThemeScheme.amber => const Color(0xFFD97706), // 琥珀橙
};
Color get _darkAccent => switch (scheme) {
// ...
AppThemeScheme.amber => const Color(0xFFFBBF24),
};
List<Color> get _lightGradient => switch (scheme) {
// ...
AppThemeScheme.amber => const [Color(0xFFD97706), Color(0xFFF59E0B)],
};
List<Color> get _darkGradient => switch (scheme) {
// ...
AppThemeScheme.amber => const [Color(0xFFFBBF24), Color(0xFFFDE68A)],
};
// 同时更新静态方法
static Color previewColor(AppThemeScheme scheme) => switch (scheme) {
// ...
AppThemeScheme.amber => const Color(0xFFD97706),
};
static String label(AppThemeScheme scheme) => switch (scheme) {
// ...
AppThemeScheme.amber => '琥珀',
};
```
### 步骤三:确认 UI 自动出现
`display_settings_page.dart` 的 `_ColorSchemeSelector` 遍历 `AppThemeScheme.values`,新方案会自动显示在设置页中,**无需额外修改**。
### 步骤四:验证
1. 运行 App,进入「设置 → 页面与展示」,确认新色块出现
2. 切换方案,确认导航栏/按钮/标签颜色随之变化
3. 切换深色模式,确认深色 accent 正确
---
## 十一、操作决策树
```
需要处理颜色?
│
├─ 主操作按钮背景渐变?
│ └─ ✅ 直接用 PrimaryActionButton,已内置 context.themeAccentGradient
│
├─ 强调色/选中态/链接色?
│ └─ ✅ 使用 context.themeAccent(随方案 + 深浅自动变化)
│
├─ 固定业务色(不随方案变化)?
│ (在线绿点、VIP 金色、气泡色等)
│ └─ ✅ 加到 AppColors 静态常量
│
├─ 需跟随深浅模式切换?
│ └─ ✅ 在 AppColorsExt 加扩展方法(context.themeXxx)
│
└─ 需要支持用户切换的全新配色方案?
└─ ✅ 按「第十节」步骤:
1. AppThemeScheme 枚举加值
2. AppAccentExtension 所有 switch 各加一条
3. 设置页自动出现,无需改 UI
```
---
*文档版本:v3.0 · 基于实际代码重写(AppAccentExtension 架构,编辑风色板)*