forked from mirrors/material_3_expressive
130 lines
4.2 KiB
Dart
130 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../tokens/color_tokens.dart';
|
|
import '../tokens/motion_tokens.dart';
|
|
import '../tokens/shape_tokens.dart';
|
|
import '../tokens/spacing_tokens.dart';
|
|
import '../tokens/typography_tokens.dart';
|
|
|
|
@immutable
|
|
class M3ETheme extends ThemeExtension<M3ETheme> {
|
|
final M3EColors colors;
|
|
final M3ETypography typography;
|
|
final M3EShapes shapes;
|
|
final M3ESpacing spacing;
|
|
final M3EMotion motion;
|
|
|
|
const M3ETheme({
|
|
required this.colors,
|
|
required this.typography,
|
|
required this.shapes,
|
|
required this.spacing,
|
|
required this.motion,
|
|
});
|
|
|
|
// Convenience proxy for commonly used text styles in packages (m3e.type.*)
|
|
_M3ETypeProxy get type => _M3ETypeProxy(typography);
|
|
|
|
static M3ETheme defaults(ColorScheme scheme) => M3ETheme(
|
|
colors: M3EColors.from(scheme),
|
|
typography: M3ETypography.defaultFor(scheme.brightness),
|
|
shapes: M3EShapes.expressive(),
|
|
spacing: const M3ESpacing.regular(),
|
|
motion: const M3EMotion.expressive(),
|
|
);
|
|
|
|
@override
|
|
M3ETheme copyWith({
|
|
M3EColors? colors,
|
|
M3ETypography? typography,
|
|
M3EShapes? shapes,
|
|
M3ESpacing? spacing,
|
|
M3EMotion? motion,
|
|
}) =>
|
|
M3ETheme(
|
|
colors: colors ?? this.colors,
|
|
typography: typography ?? this.typography,
|
|
shapes: shapes ?? this.shapes,
|
|
spacing: spacing ?? this.spacing,
|
|
motion: motion ?? this.motion,
|
|
);
|
|
|
|
@override
|
|
M3ETheme lerp(covariant M3ETheme? other, double t) {
|
|
if (other == null) return this;
|
|
return M3ETheme(
|
|
colors: M3EColors.lerp(colors, other.colors, t),
|
|
typography: M3ETypography.lerp(typography, other.typography, t),
|
|
shapes: M3EShapes.lerp(shapes, other.shapes, t),
|
|
spacing: M3ESpacing.lerp(spacing, other.spacing, t),
|
|
motion: M3EMotion.lerp(motion, other.motion, t),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Inject (or replace) the M3ETheme extension on a ThemeData.
|
|
ThemeData withM3ETheme(ThemeData base, {M3ETheme? override}) {
|
|
// Use any existing M3ETheme, else the provided override, else defaults.
|
|
final current = base.extension<M3ETheme>();
|
|
final next = override ?? current ?? M3ETheme.defaults(base.colorScheme);
|
|
|
|
// Merge existing extensions (values) with our M3ETheme, replacing prior ones.
|
|
final Iterable<ThemeExtension<dynamic>> existing = base.extensions.values;
|
|
final List<ThemeExtension<dynamic>> merged = <ThemeExtension<dynamic>>[];
|
|
for (final e in existing) {
|
|
if (e is! M3ETheme) {
|
|
merged.add(e);
|
|
}
|
|
}
|
|
merged.add(next);
|
|
|
|
return base.copyWith(extensions: merged);
|
|
}
|
|
|
|
// Internal proxy for typography shortcuts used by components.
|
|
class _M3ETypeProxy {
|
|
const _M3ETypeProxy(this._t);
|
|
final M3ETypography _t;
|
|
|
|
TextStyle get _empty => const TextStyle();
|
|
TextStyle get titleLarge => _t.base.titleLarge ?? _empty;
|
|
TextStyle get titleSmall => _t.base.titleSmall ?? _empty;
|
|
TextStyle get bodySmall => _t.base.bodySmall ?? _empty;
|
|
TextStyle get labelLarge => _t.base.labelLarge ?? _empty;
|
|
TextStyle get labelMedium => _t.base.labelMedium ?? _empty;
|
|
TextStyle get labelSmall => _t.base.labelSmall ?? _empty;
|
|
|
|
TextStyle get headlineSmallEmphasized =>
|
|
(_t.base.headlineSmall ?? _empty).merge(_t.emphasized.headline);
|
|
}
|
|
|
|
// Accessors for getting the M3E theme from ThemeData via Theme.of(context).m3e
|
|
extension M3EThemeAccessors on ThemeData {
|
|
// Returns the installed M3ETheme; in release builds falls back to defaults
|
|
M3ETheme get m3e {
|
|
final e = extension<M3ETheme>();
|
|
assert(
|
|
e != null,
|
|
'M3ETheme is not installed on ThemeData. Wrap your app theme with withM3ETheme(...)',
|
|
);
|
|
return e ?? M3ETheme.defaults(colorScheme);
|
|
}
|
|
}
|
|
|
|
// Convenience creation helpers to install M3ETheme with minimal boilerplate.
|
|
extension M3EColorSchemeAccessors on ColorScheme {
|
|
/// Creates a ThemeData from this ColorScheme and installs the M3ETheme
|
|
/// extension in one call.
|
|
///
|
|
/// Example:
|
|
/// final theme = ColorScheme.fromSeed(seedColor: Colors.teal).toM3EThemeData();
|
|
ThemeData toM3EThemeData({
|
|
bool useMaterial3 = true,
|
|
M3ETheme? override,
|
|
ThemeData? base,
|
|
}) {
|
|
final ThemeData seed =
|
|
base ?? ThemeData(useMaterial3: useMaterial3, colorScheme: this);
|
|
return withM3ETheme(seed, override: override);
|
|
}
|
|
}
|