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);
+  });
 }