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 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'theme/theme_data.dart';
|
||||||
|
|
||||||
class MinstrelApp extends StatelessWidget {
|
class MinstrelApp extends StatelessWidget {
|
||||||
const MinstrelApp({super.key});
|
const MinstrelApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Minstrel',
|
title: 'Minstrel',
|
||||||
home: Scaffold(
|
theme: buildThemeData(),
|
||||||
|
home: const Scaffold(
|
||||||
body: Center(child: Text('Minstrel — 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