Bundle Noto Sans Arabic + Noto Sans JP (SIL OFL 1.1) as per-glyph fallbacks for both the UI text theme and every generated PDF, so RTL (Arabic) and CJK render on all platforms — including desktop, where the system font may lack them — instead of tofu. A shared pdf_fonts.dart centralizes the PDF theme and the three label/catalog/recovery services reuse it. Add Japanese (ja) as the CJK reference locale — fittingly, the app's own name is Japanese (種, tane, 'seed'). Core strings translated; the rest fall back to English via slang. Wired into supportedLocales (automatic) and the settings picker. Fix three physical Alignment.centerLeft -> AlignmentDirectional.centerStart in variety_detail_screen, and make month-abbreviation and avatar-initial truncation grapheme-safe (.characters) so CJK text is never cut mid-character. Tests: CJK/Arabic PDF glyph render, ja locale resolution + English fallback. The chat-bubble mirroring flagged by the audit was verified correct (already respects Directionality) and left unchanged.
116 lines
4.8 KiB
Dart
116 lines
4.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// Material 3 palette from the Seedees v2 redesign
|
|
/// (docs/mockups/seedees_material3.html): green app bars over a pale-green
|
|
/// canvas, a saturated green for primary actions (FAB, buttons, links, category
|
|
/// headers) and a soft green container for selected/tonal surfaces.
|
|
const seedGreen = Color(0xFF2F7D34); // primary (actions, links, headers)
|
|
const seedGreenDark = Color(0xFF22521E); // status bar / darker green
|
|
// App-bar green. Nudged down from the mockups' #3E8C38 so its white title
|
|
// meets WCAG AA (4.5:1) — see test/ui/theme_contrast_test.dart.
|
|
const seedAppBar = Color(0xFF3A8434);
|
|
const seedCanvas = Color(0xFFE7F1E0); // surface / scaffold
|
|
const seedPrimaryContainer = Color(0xFFD3E8C8); // tonal green container
|
|
const seedOnPrimaryContainer = Color(0xFF2F6D2A);
|
|
const seedField = Color(0xFFF3F7EE); // outlined field fill
|
|
const seedAvatar = Color(0xFFCFE7C3); // list avatar disc
|
|
const seedOnAvatar = Color(0xFF3B7A34);
|
|
const seedOutline = Color(0xFFC3CBB6);
|
|
const seedDivider = Color(0xFFE4E9DD);
|
|
const seedTitle = Color(0xFF1B2416); // heading text on canvas
|
|
const seedOnSurface = Color(0xFF1B2416);
|
|
const seedOnSurfaceVariant = Color(0xFF5C6B52); // scientific names, secondary
|
|
// Hints and secondary text/icons. Darkened from the mockups' #8A9880 (which
|
|
// fell below WCAG AA) to keep ≥4.5:1 on white, the canvas and the field fill —
|
|
// see test/ui/theme_contrast_test.dart.
|
|
const seedMuted = Color(0xFF617057);
|
|
const seedRating = Color(0xFFE8A200); // stars
|
|
const seedFavorite = Color(0xFFE53935); // saved/favorite heart (pops on green)
|
|
const seedWarning = Color(0xFFA84600); // overdue nudges (AA on white & canvas)
|
|
|
|
ThemeData buildTaneTheme() {
|
|
final scheme =
|
|
ColorScheme.fromSeed(
|
|
seedColor: seedGreen,
|
|
brightness: Brightness.light,
|
|
).copyWith(
|
|
primary: seedGreen,
|
|
onPrimary: Colors.white,
|
|
primaryContainer: seedPrimaryContainer,
|
|
onPrimaryContainer: seedOnPrimaryContainer,
|
|
secondaryContainer: seedPrimaryContainer,
|
|
onSecondaryContainer: seedOnPrimaryContainer,
|
|
tertiary: seedRating,
|
|
onTertiary: Colors.white,
|
|
surface: seedCanvas,
|
|
onSurface: seedOnSurface,
|
|
surfaceContainerHighest: seedField,
|
|
onSurfaceVariant: seedOnSurfaceVariant,
|
|
outline: seedMuted,
|
|
outlineVariant: seedOutline,
|
|
);
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: scheme,
|
|
// Per-glyph script fallbacks so Arabic (RTL) and CJK text render even where
|
|
// the platform's default font lacks them (notably desktop). The primary
|
|
// family stays the system default for Latin; these only fill the gaps. The
|
|
// ranges are disjoint, so order is immaterial. Fonts bundled in pubspec.
|
|
fontFamilyFallback: const ['Noto Sans JP', 'Noto Sans Arabic'],
|
|
scaffoldBackgroundColor: seedCanvas,
|
|
// Green app bars — aligned with the v2 mockups and the real app.
|
|
appBarTheme: const AppBarTheme(
|
|
backgroundColor: seedAppBar,
|
|
foregroundColor: Colors.white,
|
|
surfaceTintColor: Colors.transparent,
|
|
elevation: 0,
|
|
scrolledUnderElevation: 0,
|
|
centerTitle: false,
|
|
iconTheme: IconThemeData(color: Colors.white),
|
|
titleTextStyle: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
// Squircle FAB (20px radius) as in the mockups, in the saturated primary.
|
|
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
|
backgroundColor: seedGreen,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
),
|
|
drawerTheme: const DrawerThemeData(
|
|
backgroundColor: Colors.white,
|
|
surfaceTintColor: Colors.transparent,
|
|
),
|
|
bottomSheetTheme: const BottomSheetThemeData(
|
|
backgroundColor: seedCanvas,
|
|
surfaceTintColor: Colors.transparent,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(26)),
|
|
),
|
|
),
|
|
// Pill "Save" actions across sheets and dialogs, as in the v3 mockups.
|
|
filledButtonTheme: FilledButtonThemeData(
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: seedGreen,
|
|
foregroundColor: Colors.white,
|
|
shape: const StadiumBorder(),
|
|
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 13),
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: TextButton.styleFrom(foregroundColor: seedGreen),
|
|
),
|
|
// Green floating labels on the edit-seed / add-batch text fields. (Borders
|
|
// are left to each field so the inventory search keeps its borderless pill.)
|
|
inputDecorationTheme: const InputDecorationTheme(
|
|
floatingLabelStyle: TextStyle(color: seedGreen),
|
|
),
|
|
dividerTheme: const DividerThemeData(
|
|
space: 1,
|
|
thickness: 1,
|
|
color: seedDivider,
|
|
),
|
|
);
|
|
}
|