Theme inserts IconTheme into tree (#10179)
* Let Theme insert an IconTheme into the widget tree
* flip the order, no real reason
* Let icon theme use its own fallback
* review notes
* more review notes
diff --git a/packages/flutter/lib/src/material/icon_theme.dart b/packages/flutter/lib/src/material/icon_theme.dart
index a29a78b..f6e7ed7 100644
--- a/packages/flutter/lib/src/material/icon_theme.dart
+++ b/packages/flutter/lib/src/material/icon_theme.dart
@@ -6,7 +6,6 @@
import 'package:flutter/widgets.dart';
import 'icon_theme_data.dart';
-import 'theme.dart';
/// Controls the default color, opacity, and size of icons in a widget subtree.
///
@@ -64,7 +63,7 @@
static IconThemeData _getInheritedIconThemeData(BuildContext context) {
final IconTheme iconTheme = context.inheritFromWidgetOfExactType(IconTheme);
- return iconTheme?.data ?? Theme.of(context).iconTheme;
+ return iconTheme?.data ?? const IconThemeData.fallback();
}
@override
diff --git a/packages/flutter/lib/src/material/theme.dart b/packages/flutter/lib/src/material/theme.dart
index b4dfd26..1864fea 100644
--- a/packages/flutter/lib/src/material/theme.dart
+++ b/packages/flutter/lib/src/material/theme.dart
@@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
+import 'icon_theme.dart';
import 'theme_data.dart';
export 'theme_data.dart' show Brightness, ThemeData;
@@ -20,6 +21,9 @@
/// [Theme.of]. When a widget uses [Theme.of], it is automatically rebuilt if
/// the theme later changes, so that the changes can be applied.
///
+/// The [Theme] widget implies an [IconTheme] widget, set to the value of the
+/// [ThemeData.iconTheme] of the [data] for the [Theme].
+///
/// See also:
///
/// * [ThemeData], which describes the actual configuration of a theme.
@@ -27,7 +31,7 @@
/// than changing the theme all at once.
/// * [MaterialApp], which includes an [AnimatedTheme] widget configured via
/// the [MaterialApp.theme] argument.
-class Theme extends InheritedWidget {
+class Theme extends StatelessWidget {
/// Applies the given theme [data] to [child].
///
/// The [data] and [child] arguments must not be null.
@@ -35,10 +39,10 @@
Key key,
@required this.data,
this.isMaterialAppTheme: false,
- @required Widget child
+ @required this.child,
}) : assert(child != null),
assert(data != null),
- super(key: key, child: child);
+ super(key: key);
/// Specifies the color and typography values for descendant widgets.
final ThemeData data;
@@ -54,6 +58,9 @@
/// routes, like [PopupMenuButton] and [DropdownButton], do this.
final bool isMaterialAppTheme;
+ /// The widget below this widget in the tree.
+ final Widget child;
+
static final ThemeData _kFallbackTheme = new ThemeData.fallback();
/// The data from the closest [Theme] instance that encloses the given
@@ -110,17 +117,26 @@
/// }
/// ```
static ThemeData of(BuildContext context, { bool shadowThemeOnly: false }) {
- final Theme theme = context.inheritFromWidgetOfExactType(Theme);
+ final _InheritedTheme inheritedTheme =
+ context.inheritFromWidgetOfExactType(_InheritedTheme);
if (shadowThemeOnly) {
- if (theme == null || theme.isMaterialAppTheme)
+ if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
return null;
- return theme.data;
+ return inheritedTheme.theme.data;
}
- return (theme != null) ? theme.data : _kFallbackTheme;
+ return (inheritedTheme != null) ? inheritedTheme.theme.data : _kFallbackTheme;
}
@override
- bool updateShouldNotify(Theme old) => data != old.data;
+ Widget build(BuildContext context) {
+ return new _InheritedTheme(
+ theme: this,
+ child: new IconTheme(
+ data: data.iconTheme,
+ child: child,
+ ),
+ );
+ }
@override
void debugFillDescription(List<String> description) {
@@ -129,6 +145,20 @@
}
}
+class _InheritedTheme extends InheritedWidget {
+ const _InheritedTheme({
+ Key key,
+ @required this.theme,
+ @required Widget child
+ }) : assert(theme != null),
+ super(key: key, child: child);
+
+ final Theme theme;
+
+ @override
+ bool updateShouldNotify(_InheritedTheme old) => theme != old.theme;
+}
+
/// An interpolation between two [ThemeData]s.
///
/// This class specializes the interpolation of [Tween<ThemeData>] to call the
diff --git a/packages/flutter/test/material/theme_test.dart b/packages/flutter/test/material/theme_test.dart
index 6a5ff1d..bb2ae1b 100644
--- a/packages/flutter/test/material/theme_test.dart
+++ b/packages/flutter/test/material/theme_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
@@ -231,4 +232,37 @@
expect(materials[0].color, green); // app scaffold
expect(materials[1].color, green); // dialog scaffold
});
+
+ testWidgets('IconThemes are applied', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ new MaterialApp(
+ theme: new ThemeData(iconTheme: const IconThemeData(color: Colors.green, size: 10.0)),
+ home: const Icon(Icons.computer),
+ )
+ );
+
+ RenderParagraph glyphText = tester.renderObject(find.byType(RichText));
+
+ expect(glyphText.text.style.color, Colors.green);
+ expect(glyphText.text.style.fontSize, 10.0);
+
+ await tester.pumpWidget(
+ new MaterialApp(
+ theme: new ThemeData(iconTheme: const IconThemeData(color: Colors.orange, size: 20.0)),
+ home: const Icon(Icons.computer),
+ ),
+ );
+ await tester.pump(const Duration(milliseconds: 100)); // Halfway through the theme transition
+
+ glyphText = tester.renderObject(find.byType(RichText));
+
+ expect(glyphText.text.style.color, Color.lerp(Colors.green, Colors.orange, 0.5));
+ expect(glyphText.text.style.fontSize, 15.0);
+
+ await tester.pump(const Duration(milliseconds: 100)); // Finish the transition
+ glyphText = tester.renderObject(find.byType(RichText));
+
+ expect(glyphText.text.style.color, Colors.orange);
+ expect(glyphText.text.style.fontSize, 20.0);
+ });
}