feat(studio): first-run UX, recovery affordances, l10n, bundled fonts
- Connection-aware Welcome: when the hub is down, show a hero with a primary "Start hub" CTA + install fallback instead of a dead, all-unchecked onboarding checklist (the first-run cliff). - Actionable binary-not-found (file picker + install link, not a "set FAI_BIN" dead end) and a connect-failure banner after repeated failed health polls. - Localize six hardcoded English error/toast clusters (DE+EN ARB). - Bundle Inter + JetBrains Mono as assets; drop the runtime google_fonts fetch (air-gap / KRITIS safe, no font-swap flash). Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
7511867774
commit
5313266cc4
25 changed files with 1231 additions and 234 deletions
|
|
@ -2,55 +2,75 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
class FaiTheme {
|
||||
FaiTheme._();
|
||||
|
||||
/// Bundled font families. Declared in pubspec.yaml `fonts:` and
|
||||
/// shipped as TTF assets under `assets/fonts/` — NOT fetched at
|
||||
/// runtime via google_fonts. This keeps the app working fully
|
||||
/// offline / air-gapped (KRITIS) and avoids the font-swap flash
|
||||
/// google_fonts shows on first paint.
|
||||
static const String _uiFamily = 'Inter';
|
||||
static const String _monoFamily = 'JetBrains Mono';
|
||||
|
||||
/// Inter for UI, JetBrains Mono for technical strings (IDs,
|
||||
/// paths, capability refs). Both via google_fonts so they
|
||||
/// bundle on first run; for production we'd ship them as
|
||||
/// app assets.
|
||||
/// paths, capability refs). Both bundled (see [_uiFamily]).
|
||||
static TextTheme _textTheme(Brightness b) {
|
||||
final base = b == Brightness.dark
|
||||
? Typography.whiteCupertino
|
||||
: Typography.blackCupertino;
|
||||
return GoogleFonts.interTextTheme(base).copyWith(
|
||||
displaySmall: GoogleFonts.inter(
|
||||
return base.apply(fontFamily: _uiFamily).copyWith(
|
||||
displaySmall: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.4,
|
||||
),
|
||||
headlineSmall: GoogleFonts.inter(
|
||||
headlineSmall: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
titleMedium: GoogleFonts.inter(
|
||||
titleMedium: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0,
|
||||
),
|
||||
titleSmall: GoogleFonts.inter(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
bodyLarge: GoogleFonts.inter(
|
||||
titleSmall: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyLarge: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.45,
|
||||
),
|
||||
bodyMedium: GoogleFonts.inter(
|
||||
bodyMedium: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.4,
|
||||
),
|
||||
bodySmall: GoogleFonts.inter(
|
||||
bodySmall: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.4,
|
||||
),
|
||||
labelMedium: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.w500),
|
||||
labelSmall: GoogleFonts.inter(
|
||||
labelMedium: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelSmall: const TextStyle(
|
||||
fontFamily: _uiFamily,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.2,
|
||||
|
|
@ -59,12 +79,14 @@ class FaiTheme {
|
|||
}
|
||||
|
||||
/// JetBrains Mono for monospaced technical content. Used by
|
||||
/// FaiMono helper.
|
||||
/// FaiMono helper. Only Regular (400) and Medium (500) weights
|
||||
/// are bundled — heavier requests fall back to the nearest.
|
||||
static TextStyle mono({
|
||||
double size = 12,
|
||||
FontWeight weight = FontWeight.w400,
|
||||
Color? color,
|
||||
}) => GoogleFonts.jetBrainsMono(
|
||||
}) => TextStyle(
|
||||
fontFamily: _monoFamily,
|
||||
fontSize: size,
|
||||
fontWeight: weight,
|
||||
color: color,
|
||||
|
|
@ -221,8 +243,8 @@ class FaiTheme {
|
|||
scrolledUnderElevation: 0,
|
||||
centerTitle: false,
|
||||
// Bake the foreground colour straight into the title
|
||||
// style. `headlineSmall` is built from GoogleFonts.inter
|
||||
// with no color slot, and Material's "merge foreground
|
||||
// style. `headlineSmall` is built from the bundled Inter
|
||||
// family with no color slot, and Material's "merge foreground
|
||||
// colour at draw time" path leaves it null in some
|
||||
// light-mode builds — the title rendered white-on-white.
|
||||
titleTextStyle: textTheme.headlineSmall?.copyWith(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue