Add large text switching to Gallery app. (#12443)

This lets us preview widgets in the gallery using small, normal, large, and HUGE text.

Added selections to the main drawer for these options. Defaults to "normal", obviously.
diff --git a/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart b/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart
index dc741b6..e7c9092 100644
--- a/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart
@@ -22,7 +22,7 @@
   new _Page(icon: Icons.android, text: 'ANDROID'),
   new _Page(icon: Icons.alarm, text: 'ALARM'),
   new _Page(icon: Icons.face, text: 'FACE'),
-  new _Page(icon: Icons.language, text: 'LANGAUGE'),
+  new _Page(icon: Icons.language, text: 'LANGUAGE'),
 ];
 
 class ScrollableTabsDemo extends StatefulWidget {
diff --git a/examples/flutter_gallery/lib/gallery/app.dart b/examples/flutter_gallery/lib/gallery/app.dart
index e5d814b..a07a707 100644
--- a/examples/flutter_gallery/lib/gallery/app.dart
+++ b/examples/flutter_gallery/lib/gallery/app.dart
@@ -12,15 +12,6 @@
 import 'item.dart';
 import 'updates.dart';
 
-final Map<String, WidgetBuilder> _kRoutes = new Map<String, WidgetBuilder>.fromIterable(
-  // For a different example of how to set up an application routing table,
-  // consider the Stocks example:
-  // https://github.com/flutter/flutter/blob/master/examples/stocks/lib/main.dart
-  kAllGalleryItems,
-  key: (GalleryItem item) => item.routeName,
-  value: (GalleryItem item) => item.buildRoute,
-);
-
 final ThemeData _kGalleryLightTheme = new ThemeData(
   brightness: Brightness.light,
   primarySwatch: Colors.blue,
@@ -63,6 +54,9 @@
   double _timeDilation = 1.0;
   TargetPlatform _platform;
 
+  // A null value indicates "use system default".
+  double _textScaleFactor;
+
   Timer _timeDilationTimer;
 
   @override
@@ -128,6 +122,12 @@
           }
         });
       },
+      textScaleFactor: _textScaleFactor,
+      onTextScaleFactorChanged: (double value) {
+        setState(() {
+          _textScaleFactor = value;
+        });
+      },
       onSendFeedback: widget.onSendFeedback,
     );
 
@@ -138,6 +138,33 @@
       );
     }
 
+    final Map<String, WidgetBuilder> _kRoutes = new Map<String,
+      WidgetBuilder>.fromIterable(
+      // For a different example of how to set up an application routing table
+      // using named routes, consider the example in the Navigator class documentation:
+      // https://docs.flutter.io/flutter/widgets/Navigator-class.html
+      kAllGalleryItems,
+      key: (GalleryItem item) => item.routeName,
+      value: (GalleryItem item) =>
+        (BuildContext context) {
+        if (_textScaleFactor != null) {
+          return new MediaQuery(
+            data: new MediaQueryData(textScaleFactor: _textScaleFactor),
+            child: item.buildRoute(context),
+          );
+        } else {
+          return item.buildRoute(context);
+        }
+      }
+    );
+
+    if (_textScaleFactor != null) {
+      home = new MediaQuery(
+        data: new MediaQueryData(textScaleFactor: _textScaleFactor),
+        child: home,
+      );
+    }
+
     return new MaterialApp(
       title: 'Flutter Gallery',
       color: Colors.grey,
diff --git a/examples/flutter_gallery/lib/gallery/drawer.dart b/examples/flutter_gallery/lib/gallery/drawer.dart
index 992b6a7..14f2d66 100644
--- a/examples/flutter_gallery/lib/gallery/drawer.dart
+++ b/examples/flutter_gallery/lib/gallery/drawer.dart
@@ -108,6 +108,8 @@
     @required this.onThemeChanged,
     this.timeDilation,
     @required this.onTimeDilationChanged,
+    this.textScaleFactor,
+    this.onTextScaleFactorChanged,
     this.showPerformanceOverlay,
     this.onShowPerformanceOverlayChanged,
     this.checkerboardRasterCacheImages,
@@ -126,6 +128,9 @@
   final double timeDilation;
   final ValueChanged<double> onTimeDilationChanged;
 
+  final double textScaleFactor;
+  final ValueChanged<double> onTextScaleFactorChanged;
+
   final bool showPerformanceOverlay;
   final ValueChanged<bool> onShowPerformanceOverlayChanged;
 
@@ -183,6 +188,25 @@
       selected: Theme.of(context).platform == TargetPlatform.iOS,
     );
 
+    final List<Widget> textSizeItems = <Widget>[];
+    final Map<double, String> textSizes = <double, String>{
+      null: 'System Default',
+      0.8: 'Small',
+      1.0: 'Normal',
+      1.3: 'Large',
+      2.0: 'Huge',
+    };
+    for (double size in textSizes.keys) {
+      textSizeItems.add(new RadioListTile<double>(
+        secondary: const Icon(Icons.text_fields),
+        title: new Text(textSizes[size]),
+        value: size,
+        groupValue: textScaleFactor,
+        onChanged: onTextScaleFactorChanged,
+        selected: textScaleFactor == size,
+      ));
+    }
+
     final Widget animateSlowlyItem = new CheckboxListTile(
       title: const Text('Animate Slowly'),
       value: timeDilation != 1.0,
@@ -214,11 +238,11 @@
               children: <TextSpan>[
                 new TextSpan(
                   style: aboutTextStyle,
-                  text: "Flutter is an early-stage, open-source project to help "
-                  "developers build high-performance, high-fidelity, mobile "
-                  "apps for iOS and Android from a single codebase. This "
-                  "gallery is a preview of Flutter's many widgets, behaviors, "
-                  "animations, layouts, and more. Learn more about Flutter at "
+                  text: 'Flutter is an early-stage, open-source project to help '
+                        'developers build high-performance, high-fidelity, mobile '
+                        'apps for iOS and Android from a single codebase. This '
+                        "gallery is a preview of Flutter's many widgets, behaviors, "
+                        'animations, layouts, and more. Learn more about Flutter at '
                 ),
                 new LinkTextSpan(
                   style: linkStyle,
@@ -226,7 +250,7 @@
                 ),
                 new TextSpan(
                   style: aboutTextStyle,
-                  text: ".\n\nTo see the source code for this app, please visit the "
+                  text: '.\n\nTo see the source code for this app, please visit the '
                 ),
                 new LinkTextSpan(
                   style: linkStyle,
@@ -235,7 +259,7 @@
                 ),
                 new TextSpan(
                   style: aboutTextStyle,
-                  text: "."
+                  text: '.'
                 )
               ]
             )
@@ -252,42 +276,58 @@
       mountainViewItem,
       cupertinoItem,
       const Divider(),
-      animateSlowlyItem,
-      // index 8, optional: Performance Overlay
-      sendFeedbackItem,
-      aboutItem
     ];
 
-    if (onShowPerformanceOverlayChanged != null) {
-      allDrawerItems.insert(8, new CheckboxListTile(
-        title: const Text('Performance Overlay'),
-        value: showPerformanceOverlay,
-        onChanged: onShowPerformanceOverlayChanged,
-        secondary: const Icon(Icons.assessment),
-        selected: showPerformanceOverlay,
-      ));
-    }
+    allDrawerItems.addAll(textSizeItems);
 
-    if (onCheckerboardRasterCacheImagesChanged != null) {
-      allDrawerItems.insert(8, new CheckboxListTile(
-        title: const Text('Checkerboard Raster Cache Images'),
-        value: checkerboardRasterCacheImages,
-        onChanged: onCheckerboardRasterCacheImagesChanged,
-        secondary: const Icon(Icons.assessment),
-        selected: checkerboardRasterCacheImages,
-      ));
-    }
+    allDrawerItems..addAll(<Widget>[
+      const Divider(),
+      animateSlowlyItem,
+      const Divider(),
+    ]);
 
+    bool addedOptionalItem = false;
     if (onCheckerboardOffscreenLayersChanged != null) {
-      allDrawerItems.insert(8, new CheckboxListTile(
+      allDrawerItems.add(new CheckboxListTile(
         title: const Text('Checkerboard Offscreen Layers'),
         value: checkerboardOffscreenLayers,
         onChanged: onCheckerboardOffscreenLayersChanged,
         secondary: const Icon(Icons.assessment),
         selected: checkerboardOffscreenLayers,
       ));
+      addedOptionalItem = true;
     }
 
+    if (onCheckerboardRasterCacheImagesChanged != null) {
+      allDrawerItems.add(new CheckboxListTile(
+        title: const Text('Checkerboard Raster Cache Images'),
+        value: checkerboardRasterCacheImages,
+        onChanged: onCheckerboardRasterCacheImagesChanged,
+        secondary: const Icon(Icons.assessment),
+        selected: checkerboardRasterCacheImages,
+      ));
+      addedOptionalItem = true;
+    }
+
+    if (onShowPerformanceOverlayChanged != null) {
+      allDrawerItems.add(new CheckboxListTile(
+        title: const Text('Performance Overlay'),
+        value: showPerformanceOverlay,
+        onChanged: onShowPerformanceOverlayChanged,
+        secondary: const Icon(Icons.assessment),
+        selected: showPerformanceOverlay,
+      ));
+      addedOptionalItem = true;
+    }
+
+    if (addedOptionalItem)
+      allDrawerItems.add(const Divider());
+
+    allDrawerItems.addAll(<Widget>[
+      sendFeedbackItem,
+      aboutItem,
+    ]);
+
     return new Drawer(child: new ListView(primary: false, children: allDrawerItems));
   }
 }
diff --git a/examples/flutter_gallery/lib/gallery/home.dart b/examples/flutter_gallery/lib/gallery/home.dart
index 2356cd3..70e949f 100644
--- a/examples/flutter_gallery/lib/gallery/home.dart
+++ b/examples/flutter_gallery/lib/gallery/home.dart
@@ -68,6 +68,8 @@
     @required this.onThemeChanged,
     this.timeDilation,
     @required this.onTimeDilationChanged,
+    this.textScaleFactor,
+    this.onTextScaleFactorChanged,
     this.showPerformanceOverlay,
     this.onShowPerformanceOverlayChanged,
     this.checkerboardRasterCacheImages,
@@ -86,6 +88,9 @@
   final double timeDilation;
   final ValueChanged<double> onTimeDilationChanged;
 
+  final double textScaleFactor;
+  final ValueChanged<double> onTextScaleFactorChanged;
+
   final bool showPerformanceOverlay;
   final ValueChanged<bool> onShowPerformanceOverlayChanged;
 
@@ -159,6 +164,8 @@
         onThemeChanged: widget.onThemeChanged,
         timeDilation: widget.timeDilation,
         onTimeDilationChanged: widget.onTimeDilationChanged,
+        textScaleFactor: widget.textScaleFactor,
+        onTextScaleFactorChanged: widget.onTextScaleFactorChanged,
         showPerformanceOverlay: widget.showPerformanceOverlay,
         onShowPerformanceOverlayChanged: widget.onShowPerformanceOverlayChanged,
         checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
diff --git a/examples/flutter_gallery/test/drawer_test.dart b/examples/flutter_gallery/test/drawer_test.dart
new file mode 100644
index 0000000..3eb42bd
--- /dev/null
+++ b/examples/flutter_gallery/test/drawer_test.dart
@@ -0,0 +1,100 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/scheduler.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter_gallery/gallery/app.dart';
+
+void main() {
+  final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
+  if (binding is LiveTestWidgetsFlutterBinding)
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
+
+  testWidgets('Flutter Gallery drawer item test', (WidgetTester tester) async {
+    bool hasFeedback = false;
+    void mockOnSendFeedback() {
+      hasFeedback = true;
+    }
+
+    await tester.pumpWidget(new GalleryApp(onSendFeedback: mockOnSendFeedback));
+    await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
+    await tester.pump(); // triggers a frame
+
+    final Finder finder = find.byWidgetPredicate((Widget widget) {
+      return widget is Tooltip && widget.message == 'Open navigation menu';
+    });
+    expect(finder, findsOneWidget);
+
+    // Open drawer
+    await tester.tap(finder);
+    await tester.pump(); // start animation
+    await tester.pump(const Duration(seconds: 1)); // end animation
+
+    MaterialApp app = find.byType(MaterialApp).evaluate().first.widget;
+    expect(app.theme.brightness, equals(Brightness.light));
+
+    // Change theme
+    await tester.tap(find.text('Dark'));
+    await tester.pump(); // start animation
+    await tester.pump(const Duration(seconds: 1)); // end animation
+    app = find.byType(MaterialApp).evaluate().first.widget;
+    expect(app.theme.brightness, equals(Brightness.dark));
+    expect(app.theme.platform, equals(TargetPlatform.android));
+
+    // Change platform
+    await tester.tap(find.text('iOS'));
+    await tester.pump(); // start animation
+    await tester.pump(const Duration(seconds: 1)); // end animation
+    app = find.byType(MaterialApp).evaluate().first.widget;
+    expect(app.theme.platform, equals(TargetPlatform.iOS));
+
+    // Verify the font scale.
+    final Size origTextSize = tester.getSize(find.text("Small"));
+    expect(origTextSize, equals(const Size(176.0, 14.0)));
+
+    // Switch font scale.
+    await tester.tap(find.text('Small'));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+    final Size textSize = tester.getSize(find.text("Small"));
+    expect(textSize, equals(const Size(176.0, 11.0)));
+
+    // Set font scale back to default.
+    await tester.tap(find.text('System Default'));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+    final Size newTextSize = tester.getSize(find.text("Small"));
+    expect(newTextSize, equals(origTextSize));
+
+    // Scroll to the bottom of the menu.
+    await tester.drag(find.text('Small'), const Offset(0.0, -450.0));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+
+    // Test slow animations.
+    expect(timeDilation, equals(1.0));
+    await tester.tap(find.text('Animate Slowly'));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+    expect(timeDilation, greaterThan(1.0));
+
+    // Put back time dilation (so as not to throw off tests after this one).
+    await tester.tap(find.text('Animate Slowly'));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+    expect(timeDilation, equals(1.0));
+
+    // Send feedback.
+    expect(hasFeedback, false);
+    await tester.tap(find.text('Send feedback'));
+    await tester.pump();
+    expect(hasFeedback, true);
+
+    // Close drawer
+    await tester.tap(find.byType(DrawerController));
+    await tester.pump(); // start animation
+    await tester.pump(const Duration(seconds: 1)); // end animation
+  });
+}
diff --git a/examples/flutter_gallery/test/smoke_test.dart b/examples/flutter_gallery/test/smoke_test.dart
index 26f6965..197885b 100644
--- a/examples/flutter_gallery/test/smoke_test.dart
+++ b/examples/flutter_gallery/test/smoke_test.dart
@@ -139,22 +139,31 @@
   await tester.pump(); // Start opening drawer.
   await tester.pump(const Duration(seconds: 1)); // Wait until it's really opened.
 
-  // switch theme
+  // Switch theme.
   await tester.tap(find.text('Dark'));
   await tester.pump();
   await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
 
-  // switch theme
+  // Switch theme.
   await tester.tap(find.text('Light'));
   await tester.pump();
   await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
 
-  // scroll the 'Send feedback' item into view
-  await tester.drag(find.text('Light'), const Offset(0.0, -200.0));
+  // Switch font scale.
+  await tester.tap(find.text('Small'));
+  await tester.pump();
+  await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+  // Switch font scale back to default.
+  await tester.tap(find.text('System Default'));
   await tester.pump();
   await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
 
-  // send feedback
+  // Scroll the 'Send feedback' item into view.
+  await tester.drag(find.text('Small'), const Offset(0.0, -450.0));
+  await tester.pump();
+  await tester.pump(const Duration(seconds: 1)); // Wait until it's changed.
+
+  // Send feedback.
   expect(hasFeedback, false);
   await tester.tap(find.text('Send feedback'));
   await tester.pump();