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>
This commit is contained in:
+107
-19
@@ -1,21 +1,87 @@
|
||||
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(0xFF4F46E5);
|
||||
static const canvas = Color(0xFFF8FAFC);
|
||||
static const ink = Color(0xFF0F172A);
|
||||
static const muted = Color(0xFF64748B);
|
||||
static const border = Color(0xFFE2E8F0);
|
||||
static const success = Color(0xFF10B981);
|
||||
/// 品牌珊瑚橙:图标、装饰、渐变起点;不用于承载文字的实心填充(对比度不足)。
|
||||
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: Colors.white,
|
||||
);
|
||||
surface: AppColors.surface,
|
||||
).copyWith(error: AppColors.error, onError: Colors.white);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
@@ -48,40 +114,62 @@ ThemeData buildAppTheme() {
|
||||
bodySmall: TextStyle(color: AppColors.muted, fontSize: 12, height: 1.4),
|
||||
),
|
||||
cardTheme: const CardThemeData(
|
||||
color: Colors.white,
|
||||
color: AppColors.surface,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
side: BorderSide(color: Color(0xFFF1F5F9)),
|
||||
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: Colors.white,
|
||||
fillColor: AppColors.surface,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(18)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
|
||||
borderSide: BorderSide(color: AppColors.border),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(18)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(AppRadius.lg)),
|
||||
borderSide: BorderSide(color: AppColors.border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(18)),
|
||||
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(16)),
|
||||
borderRadius: BorderRadius.all(Radius.circular(AppRadius.md)),
|
||||
),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: Colors.white,
|
||||
indicatorColor: const Color(0xFFE0E7FF),
|
||||
backgroundColor: AppColors.surface,
|
||||
indicatorColor: AppColors.surfaceTint,
|
||||
height: 72,
|
||||
labelTextStyle: WidgetStateProperty.resolveWith((states) {
|
||||
return TextStyle(
|
||||
@@ -90,7 +178,7 @@ ThemeData buildAppTheme() {
|
||||
? FontWeight.w700
|
||||
: FontWeight.w500,
|
||||
color: states.contains(WidgetState.selected)
|
||||
? AppColors.primary
|
||||
? AppColors.primaryStrong
|
||||
: AppColors.muted,
|
||||
);
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user