Files
patbond-flutter/lib/core/theme/app_theme.dart
T
lixi af002edde3 feat: 迁移珊瑚橙主题体系并新增认证基础组件(ADR-005)
- app_theme.dart 重写为语义 token(AppColors/AppRadius),落地 primaryStrong/error 等 AA 对比度色
- 五个页面与公共组件的旧靛蓝硬编码色值全部替换为 token,仅换色不动布局
- 新增 BrandMark/AppTextField/PrimaryButton/InlineErrorBanner/AuthScaffold 及 6 个 widget 测试
- 门禁:dart format(0 changed)/ flutter analyze(0 issues)/ flutter test(7 passed)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 10:34:18 +08:00

188 lines
6.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
/// 语义色 tokenADR-005:以 AI宠物_iOS_UI设计稿.html 珊瑚橙暖色系为品牌正典,
/// 详见 iteration-1-reports/04-ui-login-design-spec.md §1.1)。
abstract final class AppColors {
/// 品牌珊瑚橙:图标、装饰、渐变起点;不用于承载文字的实心填充(对比度不足)。
static const primary = Color(0xFFFF6F4C);
/// 加深珊瑚:实心按钮填充、可点击文字链接(白字约 4.5:1,WCAG AA)。
static const primaryStrong = Color(0xFFD6431A);
/// 浅色底上的强调文字、按钮反白替代。
static const primaryDark = Color(0xFF7A2E12);
/// amber:渐变终点、徽章、会员/促销 CTA。
static const accent = Color(0xFFFFB648);
/// amber 底上的文字。
static const accentDark = Color(0xFF7A4B0A);
/// 页面背景(奶油底)。
static const canvas = Color(0xFFFFF7ED);
/// 卡片、输入框背景。
static const surface = Color(0xFFFFFFFF);
/// peach:图标底、占位块、选中指示。
static const surfaceTint = Color(0xFFFFE8D6);
/// 主文字。
static const ink = Color(0xFF3E2A1F);
/// 次级文字、占位符。
static const muted = Color(0xFF9C8977);
/// 描边、分隔线。
static const border = Color(0xFFF0DCC8);
/// sage:成功提示的图标/描边。
static const success = Color(0xFF7FA88A);
/// 成功提示底上的文字。
static const successInk = Color(0xFF3F5744);
/// 成功提示底色。
static const successSurface = Color(0xFFE8F0E8);
static const warning = Color(0xFFF59E0B);
/// 校验错误文字、错误描边、失败提示(白底上约 5.4:1,AA 达标)。
static const error = Color(0xFFD0342C);
/// 品牌渐变:hero 区、头像环、装饰性大面积(不承载正文文字)。
static const brandGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [primary, accent],
);
}
/// 圆角 token(设计规范 §1.3)。
abstract final class AppRadius {
/// 小按钮、内嵌 CTA、错误横幅。
static const sm = 12.0;
/// 主按钮、SnackBar、菜单组。
static const md = 16.0;
/// 输入框、feed 卡。
static const lg = 18.0;
/// 大卡片。
static const xl = 24.0;
/// 芯片、徽章。
static const pill = 999.0;
}
ThemeData buildAppTheme() {
final scheme = ColorScheme.fromSeed(
seedColor: AppColors.primary,
brightness: Brightness.light,
surface: AppColors.surface,
).copyWith(error: AppColors.error, onError: Colors.white);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: AppColors.canvas,
fontFamilyFallback: const [
'Noto Sans CJK SC',
'Noto Sans SC',
'PingFang SC',
'Microsoft YaHei',
'sans-serif',
],
textTheme: const TextTheme(
headlineSmall: TextStyle(
color: AppColors.ink,
fontSize: 22,
fontWeight: FontWeight.w800,
),
titleLarge: TextStyle(
color: AppColors.ink,
fontSize: 18,
fontWeight: FontWeight.w800,
),
titleMedium: TextStyle(
color: AppColors.ink,
fontSize: 15,
fontWeight: FontWeight.w700,
),
bodyMedium: TextStyle(color: AppColors.ink, fontSize: 14, height: 1.5),
bodySmall: TextStyle(color: AppColors.muted, fontSize: 12, height: 1.4),
),
cardTheme: const CardThemeData(
color: AppColors.surface,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.xl)),
side: BorderSide(color: AppColors.border),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: AppColors.primaryStrong,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.ink.withAlpha(31),
disabledForegroundColor: AppColors.ink.withAlpha(97),
minimumSize: const Size(64, 52),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.md)),
),
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
),
),
inputDecorationTheme: const InputDecorationTheme(
filled: true,
fillColor: AppColors.surface,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.primary, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.error, width: 1.5),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
borderSide: BorderSide(color: AppColors.error, width: 1.5),
),
errorStyle: TextStyle(color: AppColors.error, fontSize: 12),
),
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppRadius.md)),
),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.surface,
indicatorColor: AppColors.surfaceTint,
height: 72,
labelTextStyle: WidgetStateProperty.resolveWith((states) {
return TextStyle(
fontSize: 11,
fontWeight: states.contains(WidgetState.selected)
? FontWeight.w700
: FontWeight.w500,
color: states.contains(WidgetState.selected)
? AppColors.primaryStrong
: AppColors.muted,
);
}),
),
);
}