← API | 列表 | 配色方案扩展指南
提示信息
# 同伴 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 架构,编辑风色板)*