feat(client): 字体可注入 + 启用 golden 视觉回归闸
- pangolin_theme: PangolinFonts 加 useBundled 开关 + sora/manrope/jetBrainsMono/ manropeTextTheme 封装(生产走 google_fonts / 测试走 bundled family); PangolinText 与 _build 改调封装,移除散落的 GoogleFonts 直接调用 - test/flutter_test_config.dart: 全测试前置,FontLoader 注册 test/fonts 真实 Sora/Manrope/JetBrainsMono + Lucide 图标字体,置 useBundled=true - 启用 test/golden(去 @Skip):连接键三态+推荐卡+额度卡×明暗 12 基准图 - tool/visual-diff.md: 标注 golden 严格闸已落地 测试: flutter test 76 通过(原 64+1skip → 76 无 skip), analyze 0 error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -143,15 +143,45 @@ extension PangolinContext on BuildContext {
|
||||
PangolinScheme get pangolin => Theme.of(this).extension<PangolinScheme>()!;
|
||||
}
|
||||
|
||||
/// ── Font families (via google_fonts at runtime) ─────────────────────
|
||||
/// ── Font families ───────────────────────────────────────────────────
|
||||
/// 生产用 google_fonts 运行时加载;测试态设 [useBundled]=true,改用本地
|
||||
/// FontLoader 注册的同名 family(避免 google_fonts 在无网/禁拉取时抛异常,
|
||||
/// 让 golden 截图回归可跑)。封装函数是 PangolinText/_build 的唯一字体入口。
|
||||
class PangolinFonts {
|
||||
PangolinFonts._();
|
||||
// String constants used as display labels only.
|
||||
// Actual font loading is done via google_fonts in PangolinText getters.
|
||||
static const display = 'Sora';
|
||||
static const sans = 'Manrope';
|
||||
static const cjk = 'Noto Sans SC';
|
||||
static const mono = 'JetBrains Mono';
|
||||
|
||||
/// 测试钩子:true 时用 bundled family(test/fonts/ 经 FontLoader 注册),不走 google_fonts。
|
||||
static bool useBundled = false;
|
||||
|
||||
static TextStyle sora({double? fontSize, FontWeight? fontWeight, double? height, double? letterSpacing}) =>
|
||||
useBundled
|
||||
? TextStyle(fontFamily: display, fontSize: fontSize, fontWeight: fontWeight, height: height, letterSpacing: letterSpacing)
|
||||
: GoogleFonts.sora(fontSize: fontSize, fontWeight: fontWeight, height: height, letterSpacing: letterSpacing);
|
||||
|
||||
static TextStyle manrope(
|
||||
{double? fontSize, FontWeight? fontWeight, double? height, double? letterSpacing, List<FontFeature>? fontFeatures}) =>
|
||||
useBundled
|
||||
? TextStyle(
|
||||
fontFamily: sans,
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
height: height,
|
||||
letterSpacing: letterSpacing,
|
||||
fontFeatures: fontFeatures)
|
||||
: GoogleFonts.manrope(
|
||||
fontSize: fontSize, fontWeight: fontWeight, height: height, letterSpacing: letterSpacing, fontFeatures: fontFeatures);
|
||||
|
||||
static TextStyle jetBrainsMono({double? fontSize, FontWeight? fontWeight, List<FontFeature>? fontFeatures}) =>
|
||||
useBundled
|
||||
? TextStyle(fontFamily: mono, fontSize: fontSize, fontWeight: fontWeight, fontFeatures: fontFeatures)
|
||||
: GoogleFonts.jetBrainsMono(fontSize: fontSize, fontWeight: fontWeight, fontFeatures: fontFeatures);
|
||||
|
||||
static TextTheme manropeTextTheme(TextTheme base) =>
|
||||
useBundled ? base.apply(fontFamily: sans) : GoogleFonts.manropeTextTheme(base);
|
||||
}
|
||||
|
||||
/// ── Type scale ──────────────────────────────────────────────────────
|
||||
@@ -161,27 +191,27 @@ class PangolinFonts {
|
||||
class PangolinText {
|
||||
PangolinText._();
|
||||
|
||||
static TextStyle get displayXl => GoogleFonts.sora(
|
||||
static TextStyle get displayXl => PangolinFonts.sora(
|
||||
fontSize: 48, fontWeight: FontWeight.w700, height: 1.15, letterSpacing: -0.96);
|
||||
static TextStyle get display => GoogleFonts.sora(
|
||||
static TextStyle get display => PangolinFonts.sora(
|
||||
fontSize: 36, fontWeight: FontWeight.w700, height: 1.15, letterSpacing: -0.72);
|
||||
static TextStyle get h1 => GoogleFonts.sora(
|
||||
static TextStyle get h1 => PangolinFonts.sora(
|
||||
fontSize: 30, fontWeight: FontWeight.w600, height: 1.3, letterSpacing: -0.3);
|
||||
static TextStyle get h2 => GoogleFonts.sora(
|
||||
static TextStyle get h2 => PangolinFonts.sora(
|
||||
fontSize: 24, fontWeight: FontWeight.w600, height: 1.3, letterSpacing: -0.24);
|
||||
static TextStyle get h3 => GoogleFonts.manrope(
|
||||
static TextStyle get h3 => PangolinFonts.manrope(
|
||||
fontSize: 20, fontWeight: FontWeight.w600, height: 1.3);
|
||||
static TextStyle get bodyLg => GoogleFonts.manrope(
|
||||
static TextStyle get bodyLg => PangolinFonts.manrope(
|
||||
fontSize: 18, fontWeight: FontWeight.w400, height: 1.65);
|
||||
static TextStyle get body => GoogleFonts.manrope(
|
||||
static TextStyle get body => PangolinFonts.manrope(
|
||||
fontSize: 16, fontWeight: FontWeight.w400, height: 1.5);
|
||||
static TextStyle get sm => GoogleFonts.manrope(
|
||||
static TextStyle get sm => PangolinFonts.manrope(
|
||||
fontSize: 14, fontWeight: FontWeight.w400, height: 1.5);
|
||||
static TextStyle get caption => GoogleFonts.manrope(
|
||||
static TextStyle get caption => PangolinFonts.manrope(
|
||||
fontSize: 12, fontWeight: FontWeight.w500, height: 1.5);
|
||||
static TextStyle get overline => GoogleFonts.manrope(
|
||||
static TextStyle get overline => PangolinFonts.manrope(
|
||||
fontSize: 12, fontWeight: FontWeight.w600, letterSpacing: 0.96);
|
||||
static TextStyle get mono => GoogleFonts.jetBrainsMono(
|
||||
static TextStyle get mono => PangolinFonts.jetBrainsMono(
|
||||
fontSize: 14, fontWeight: FontWeight.w400,
|
||||
fontFeatures: const [FontFeature.tabularFigures()]);
|
||||
}
|
||||
@@ -210,8 +240,8 @@ class PangolinTheme {
|
||||
brightness: b,
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: s.bg,
|
||||
// Use google_fonts Manrope as app-wide base font
|
||||
textTheme: GoogleFonts.manropeTextTheme(
|
||||
// Manrope app-wide base font(生产 google_fonts / 测试 bundled)
|
||||
textTheme: PangolinFonts.manropeTextTheme(
|
||||
ThemeData(brightness: b).textTheme,
|
||||
),
|
||||
extensions: [s],
|
||||
@@ -219,7 +249,7 @@ class PangolinTheme {
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: s.accent,
|
||||
foregroundColor: s.fgOnAccent,
|
||||
textStyle: GoogleFonts.manrope(fontWeight: FontWeight.w600, fontSize: 15),
|
||||
textStyle: PangolinFonts.manrope(fontWeight: FontWeight.w600, fontSize: 15),
|
||||
shape: const StadiumBorder(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 14),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user