From 0ceaac00a0161043d8d5c8ac9b4a6c0d56963efa Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 14 Sep 2022 21:51:41 +0200 Subject: [PATCH] Introduce theme data overrides Allow overriding a few ThemeData aspects (the "configuration" section) when using the YaruTheme convenience widget. --- lib/src/widgets/inherited_theme.dart | 99 ++++++++++++++++++++++++++-- pubspec.yaml | 1 + test/widget_test.dart | 26 ++++++++ 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/lib/src/widgets/inherited_theme.dart b/lib/src/widgets/inherited_theme.dart index 569375f73..59e4dcede 100644 --- a/lib/src/widgets/inherited_theme.dart +++ b/lib/src/widgets/inherited_theme.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:platform/platform.dart'; @@ -56,8 +57,8 @@ YaruVariant? _detectYaruVariant(Platform platform) { /// YaruTheme( /// builder: (context, yaru, child) { /// return MaterialApp( -/// theme: yaru.variant?.theme, -/// darkTheme: yaru.variant?.darkTheme, +/// theme: yaru.theme, +/// darkTheme: yaru.darkTheme, /// home: ... /// ); /// }, @@ -70,6 +71,28 @@ YaruVariant? _detectYaruVariant(Platform platform) { /// that any widget created by [MaterialApp], such as the built-in [Navigator], /// gains Yaru-theme as well. /// +/// ### Theme data overrides +/// +/// The [data] property can be used to override parts of the default theme data. +/// For example, the following code overrides the default page transitions and +/// visual density: +/// +/// ```dart +/// YaruTheme( +/// data: YaruThemeData( +/// pageTransitionsTheme: PageTransitionsTheme(/*...*/), +/// visualDensity: VisualDensity(horizontal: -4, vertical: -4), +/// ), +/// builder: (context, yaru, child) { +/// return MaterialApp( +/// theme: yaru.theme, +/// darkTheme: yaru.darkTheme, +/// home: ... +/// ); +/// }, +/// ) +/// ``` +/// /// See also: /// * [YaruThemeData] class YaruTheme extends StatefulWidget { @@ -177,7 +200,7 @@ class _YaruThemeState extends State { } YaruThemeData resolveData() { - return YaruThemeData( + return widget.data.copyWith( variant: widget.data.variant ?? _variant, highContrast: widget.data.highContrast ?? MediaQuery.highContrastOf(context), @@ -193,7 +216,7 @@ class _YaruThemeState extends State { } final variant = data.variant ?? YaruVariant.orange; - return dark ? variant.darkTheme : variant.theme; + return (dark ? variant.darkTheme : variant.theme).overrideWith(data); } @override @@ -216,6 +239,10 @@ class YaruThemeData with Diagnosticable { this.variant, this.highContrast, this.themeMode, + this.extensions, + this.pageTransitionsTheme, + this.useMaterial3, + this.visualDensity, }); /// Specifies the theme variant. @@ -227,16 +254,45 @@ class YaruThemeData with Diagnosticable { /// Whether a light or dark theme is used. final ThemeMode? themeMode; + /// Overrides [ThemeData.extensions]. + final Iterable>? extensions; + + /// Overrides [ThemeData.pageTransitionsTheme]. + final PageTransitionsTheme? pageTransitionsTheme; + + /// Overrides [ThemeData.useMaterial3]. + final bool? useMaterial3; + + /// Overrides [ThemeData.visualDensity]. + final VisualDensity? visualDensity; + + /// The light theme of [variant] (or [yaruLight] if not available) merged with + /// the `YaruThemeData` overrides. + ThemeData? get theme => (variant?.theme ?? yaruLight).overrideWith(this); + + /// The dark theme of [variant] (or [yaruDark] if not available) merged with + /// the `YaruThemeData` overrides. + ThemeData? get darkTheme => + (variant?.darkTheme ?? yaruDark).overrideWith(this); + /// Creates a copy of this [YaruThemeData] with the provided values. YaruThemeData copyWith({ YaruVariant? variant, bool? highContrast, ThemeMode? themeMode, + Iterable>? extensions, + PageTransitionsTheme? pageTransitionsTheme, + bool? useMaterial3, + VisualDensity? visualDensity, }) { return YaruThemeData( variant: variant ?? this.variant, highContrast: highContrast ?? this.highContrast, themeMode: themeMode ?? this.themeMode, + extensions: extensions ?? this.extensions, + pageTransitionsTheme: pageTransitionsTheme ?? this.pageTransitionsTheme, + useMaterial3: useMaterial3 ?? this.useMaterial3, + visualDensity: visualDensity ?? this.visualDensity, ); } @@ -246,19 +302,39 @@ class YaruThemeData with Diagnosticable { properties.add(DiagnosticsProperty('variant', variant)); properties.add(DiagnosticsProperty('highContrast', highContrast)); properties.add(DiagnosticsProperty('themeMode', themeMode)); + properties.add(IterableProperty('extensions', extensions)); + properties + .add(DiagnosticsProperty('pageTransitionsTheme', pageTransitionsTheme)); + properties.add(DiagnosticsProperty('useMaterial3', useMaterial3)); + properties.add(DiagnosticsProperty('visualDensity', visualDensity)); } @override bool operator ==(Object other) { if (identical(this, other)) return true; + final iterableEquals = const IterableEquality().equals; return other is YaruThemeData && other.variant == variant && other.highContrast == highContrast && - other.themeMode == themeMode; + other.themeMode == themeMode && + iterableEquals(other.extensions, extensions) && + other.pageTransitionsTheme == pageTransitionsTheme && + other.useMaterial3 == useMaterial3 && + other.visualDensity == visualDensity; } @override - int get hashCode => Object.hash(variant, highContrast, themeMode); + int get hashCode { + return Object.hash( + variant, + highContrast, + themeMode, + extensions, + pageTransitionsTheme, + useMaterial3, + visualDensity, + ); + } } class _YaruInheritedTheme extends InheritedTheme { @@ -279,3 +355,14 @@ class _YaruInheritedTheme extends InheritedTheme { return _YaruInheritedTheme(data: data, child: child); } } + +extension _YaruThemeDataX on ThemeData { + ThemeData overrideWith(YaruThemeData data) { + return copyWith( + extensions: data.extensions, + pageTransitionsTheme: data.pageTransitionsTheme, + useMaterial3: data.useMaterial3, + visualDensity: data.visualDensity, + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 18e7941da..15bba0617 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,6 +10,7 @@ environment: flutter: ">=3.3.0" dependencies: + collection: ^1.16.0 flutter: sdk: flutter platform: ^3.1.0 diff --git a/test/widget_test.dart b/test/widget_test.dart index 850ea9274..a965520d9 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -149,6 +149,24 @@ void main() { expect(YaruTheme.of(context).themeMode, ThemeMode.dark); }); }); + + testWidgets('theme data overrides', (tester) async { + const extensions = []; + const pageTransitionsTheme = PageTransitionsTheme(); + const visualDensity = VisualDensity(horizontal: -4, vertical: -4); + await tester.pumpTheme( + extensions: extensions, + pageTransitionsTheme: pageTransitionsTheme, + useMaterial3: false, + visualDensity: visualDensity, + ); + final context = tester.element(find.byType(Container)); + final theme = YaruTheme.of(context); + expect(theme.extensions, same(extensions)); + expect(theme.pageTransitionsTheme, same(pageTransitionsTheme)); + expect(theme.useMaterial3, isFalse); + expect(theme.visualDensity, visualDensity); + }); } MockYaruSettings createMockSettings({String theme = ''}) { @@ -163,6 +181,10 @@ extension ThemeTester on WidgetTester { YaruVariant? variant, bool? highContrast, ThemeMode? themeMode, + Iterable>? extensions, + PageTransitionsTheme? pageTransitionsTheme, + bool? useMaterial3, + VisualDensity? visualDensity, String desktop = '', YaruSettings? settings, }) async { @@ -170,6 +192,10 @@ extension ThemeTester on WidgetTester { variant: variant, highContrast: highContrast, themeMode: themeMode, + extensions: extensions, + pageTransitionsTheme: pageTransitionsTheme, + useMaterial3: useMaterial3, + visualDensity: visualDensity, ); await pumpWidget( MaterialApp(