feat(flutter): FabledSwordTheme ThemeExtension + Material 3 ThemeData
Widgets read tokens via Theme.of(ctx).extension<FabledSwordTheme>(). Material 3 ColorScheme bound to FabledSword tokens. Body text uses GoogleFonts.interTextTheme() so the fonts work without bundling .ttf files (offline-from-cold-launch is a separate question once #357 lands).
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'theme/theme_data.dart';
|
||||
|
||||
class MinstrelApp extends StatelessWidget {
|
||||
const MinstrelApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
return MaterialApp(
|
||||
title: 'Minstrel',
|
||||
home: Scaffold(
|
||||
theme: buildThemeData(),
|
||||
home: const Scaffold(
|
||||
body: Center(child: Text('Minstrel — scaffold')),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'theme_extension.dart';
|
||||
import 'tokens.dart';
|
||||
|
||||
ThemeData buildThemeData() {
|
||||
final fs = FabledSwordTheme.fromTokens();
|
||||
final colorScheme = ColorScheme.dark(
|
||||
surface: fs.iron,
|
||||
onSurface: fs.parchment,
|
||||
primary: fs.accent,
|
||||
onPrimary: fs.parchment,
|
||||
secondary: fs.moss,
|
||||
error: fs.error,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: fs.obsidian,
|
||||
textTheme: GoogleFonts.interTextTheme().apply(
|
||||
bodyColor: fs.parchment,
|
||||
displayColor: fs.parchment,
|
||||
),
|
||||
fontFamily: FabledSwordTokens.fontBody,
|
||||
extensions: <ThemeExtension<dynamic>>[fs],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
class FabledSwordTheme extends ThemeExtension<FabledSwordTheme> {
|
||||
const FabledSwordTheme({
|
||||
required this.accent,
|
||||
required this.obsidian,
|
||||
required this.iron,
|
||||
required this.slate,
|
||||
required this.pewter,
|
||||
required this.parchment,
|
||||
required this.vellum,
|
||||
required this.ash,
|
||||
required this.moss,
|
||||
required this.bronze,
|
||||
required this.oxblood,
|
||||
required this.warning,
|
||||
required this.error,
|
||||
required this.info,
|
||||
required this.display,
|
||||
required this.body,
|
||||
required this.mono,
|
||||
});
|
||||
|
||||
final Color accent;
|
||||
final Color obsidian, iron, slate, pewter;
|
||||
final Color parchment, vellum, ash;
|
||||
final Color moss, bronze, oxblood;
|
||||
final Color warning, error, info;
|
||||
final TextStyle display, body, mono;
|
||||
|
||||
static FabledSwordTheme fromTokens() => FabledSwordTheme(
|
||||
accent: FabledSwordTokens.accent,
|
||||
obsidian: FabledSwordTokens.obsidian,
|
||||
iron: FabledSwordTokens.iron,
|
||||
slate: FabledSwordTokens.slate,
|
||||
pewter: FabledSwordTokens.pewter,
|
||||
parchment: FabledSwordTokens.parchment,
|
||||
vellum: FabledSwordTokens.vellum,
|
||||
ash: FabledSwordTokens.ash,
|
||||
moss: FabledSwordTokens.moss,
|
||||
bronze: FabledSwordTokens.bronze,
|
||||
oxblood: FabledSwordTokens.oxblood,
|
||||
warning: FabledSwordTokens.warning,
|
||||
error: FabledSwordTokens.error,
|
||||
info: FabledSwordTokens.info,
|
||||
display: const TextStyle(fontFamily: FabledSwordTokens.fontDisplay),
|
||||
body: const TextStyle(fontFamily: FabledSwordTokens.fontBody),
|
||||
mono: const TextStyle(fontFamily: FabledSwordTokens.fontMono),
|
||||
);
|
||||
|
||||
@override
|
||||
FabledSwordTheme copyWith({
|
||||
Color? accent,
|
||||
}) =>
|
||||
FabledSwordTheme(
|
||||
accent: accent ?? this.accent,
|
||||
obsidian: obsidian, iron: iron, slate: slate, pewter: pewter,
|
||||
parchment: parchment, vellum: vellum, ash: ash,
|
||||
moss: moss, bronze: bronze, oxblood: oxblood,
|
||||
warning: warning, error: error, info: info,
|
||||
display: display, body: body, mono: mono,
|
||||
);
|
||||
|
||||
@override
|
||||
FabledSwordTheme lerp(ThemeExtension<FabledSwordTheme>? other, double t) => this;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:minstrel/theme/theme_data.dart';
|
||||
import 'package:minstrel/theme/theme_extension.dart';
|
||||
import 'package:minstrel/theme/tokens.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Theme exposes FabledSwordTheme with expected accent', (tester) async {
|
||||
late FabledSwordTheme captured;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: buildThemeData(),
|
||||
home: Builder(
|
||||
builder: (ctx) {
|
||||
captured = Theme.of(ctx).extension<FabledSwordTheme>()!;
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(captured.accent, FabledSwordTokens.accent);
|
||||
expect(captured.parchment, FabledSwordTokens.parchment);
|
||||
expect(captured.body.fontFamily, FabledSwordTokens.fontBody);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user