af002edde3
- 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>
188 lines
6.1 KiB
Dart
188 lines
6.1 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
/// 语义色 token(ADR-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,
|
||
);
|
||
}),
|
||
),
|
||
);
|
||
}
|