Added ThemeMode support to the Flutter Gallery (#36399)

Made the Flutter Gallery use the new ThemeMode property so that it can
use the system's dark/light mode setting by default.
diff --git a/examples/flutter_gallery/lib/gallery/app.dart b/examples/flutter_gallery/lib/gallery/app.dart
index a502087..b21f77f 100644
--- a/examples/flutter_gallery/lib/gallery/app.dart
+++ b/examples/flutter_gallery/lib/gallery/app.dart
@@ -62,7 +62,7 @@
   void initState() {
     super.initState();
     _options = GalleryOptions(
-      theme: kLightGalleryTheme,
+      themeMode: ThemeMode.system,
       textScaleFactor: kAllGalleryTextScaleValues[0],
       timeDilation: timeDilation,
       platform: defaultTargetPlatform,
@@ -134,7 +134,9 @@
     return ScopedModel<AppStateModel>(
       model: model,
       child: MaterialApp(
-        theme: _options.theme.data.copyWith(platform: _options.platform),
+        theme: kLightGalleryTheme.copyWith(platform: _options.platform),
+        darkTheme: kDarkGalleryTheme.copyWith(platform: _options.platform),
+        themeMode: _options.themeMode,
         title: 'Flutter Gallery',
         color: Colors.grey,
         showPerformanceOverlay: _options.showPerformanceOverlay,
@@ -148,12 +150,14 @@
               // Specifically use a blank Cupertino theme here and do not transfer
               // over the Material primary color etc except the brightness to
               // showcase standard iOS looks.
-              CupertinoTheme(
-                data: CupertinoThemeData(
-                  brightness: _options.theme.data.brightness,
-                ),
-                child: child,
-              ),
+              Builder(builder: (BuildContext context) {
+                return CupertinoTheme(
+                  data: CupertinoThemeData(
+                    brightness: Theme.of(context).brightness,
+                  ),
+                  child: child,
+                );
+              }),
             ),
           );
         },
diff --git a/examples/flutter_gallery/lib/gallery/options.dart b/examples/flutter_gallery/lib/gallery/options.dart
index c5642be..6076d01 100644
--- a/examples/flutter_gallery/lib/gallery/options.dart
+++ b/examples/flutter_gallery/lib/gallery/options.dart
@@ -6,11 +6,10 @@
 
 import 'about.dart';
 import 'scales.dart';
-import 'themes.dart';
 
 class GalleryOptions {
   GalleryOptions({
-    this.theme,
+    this.themeMode,
     this.textScaleFactor,
     this.textDirection = TextDirection.ltr,
     this.timeDilation = 1.0,
@@ -20,7 +19,7 @@
     this.showPerformanceOverlay = false,
   });
 
-  final GalleryTheme theme;
+  final ThemeMode themeMode;
   final GalleryTextScaleValue textScaleFactor;
   final TextDirection textDirection;
   final double timeDilation;
@@ -30,7 +29,7 @@
   final bool showOffscreenLayersCheckerboard;
 
   GalleryOptions copyWith({
-    GalleryTheme theme,
+    ThemeMode themeMode,
     GalleryTextScaleValue textScaleFactor,
     TextDirection textDirection,
     double timeDilation,
@@ -40,7 +39,7 @@
     bool showOffscreenLayersCheckerboard,
   }) {
     return GalleryOptions(
-      theme: theme ?? this.theme,
+      themeMode: themeMode ?? this.themeMode,
       textScaleFactor: textScaleFactor ?? this.textScaleFactor,
       textDirection: textDirection ?? this.textDirection,
       timeDilation: timeDilation ?? this.timeDilation,
@@ -56,7 +55,7 @@
     if (runtimeType != other.runtimeType)
       return false;
     final GalleryOptions typedOther = other;
-    return theme == typedOther.theme
+    return themeMode == typedOther.themeMode
         && textScaleFactor == typedOther.textScaleFactor
         && textDirection == typedOther.textDirection
         && platform == typedOther.platform
@@ -67,7 +66,7 @@
 
   @override
   int get hashCode => hashValues(
-    theme,
+    themeMode,
     textScaleFactor,
     textDirection,
     timeDilation,
@@ -79,7 +78,7 @@
 
   @override
   String toString() {
-    return '$runtimeType($theme)';
+    return '$runtimeType($themeMode)';
   }
 }
 
@@ -202,25 +201,55 @@
   }
 }
 
-class _ThemeItem extends StatelessWidget {
-  const _ThemeItem(this.options, this.onOptionsChanged);
+class _ThemeModeItem extends StatelessWidget {
+  const _ThemeModeItem(this.options, this.onOptionsChanged);
 
   final GalleryOptions options;
   final ValueChanged<GalleryOptions> onOptionsChanged;
 
+  static final Map<ThemeMode, String> modeLabels = <ThemeMode, String>{
+    ThemeMode.system: 'System Default',
+    ThemeMode.light: 'Light',
+    ThemeMode.dark: 'Dark',
+  };
+
   @override
   Widget build(BuildContext context) {
-    return _BooleanItem(
-      'Dark Theme',
-      options.theme == kDarkGalleryTheme,
-      (bool value) {
-        onOptionsChanged(
-          options.copyWith(
-            theme: value ? kDarkGalleryTheme : kLightGalleryTheme,
+    return _OptionsItem(
+      child: Row(
+        children: <Widget>[
+          Expanded(
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: <Widget>[
+                const Text('Theme'),
+                Text(
+                  '${modeLabels[options.themeMode]}',
+                  style: Theme.of(context).primaryTextTheme.body1,
+                ),
+              ],
+            ),
           ),
-        );
-      },
-      switchKey: const Key('dark_theme'),
+          PopupMenuButton<ThemeMode>(
+            padding: const EdgeInsetsDirectional.only(end: 16.0),
+            icon: const Icon(Icons.arrow_drop_down),
+            initialValue: options.themeMode,
+            itemBuilder: (BuildContext context) {
+              return ThemeMode.values.map<PopupMenuItem<ThemeMode>>((ThemeMode mode) {
+                return PopupMenuItem<ThemeMode>(
+                  value: mode,
+                  child: Text(modeLabels[mode]),
+                );
+              }).toList();
+            },
+            onSelected: (ThemeMode mode) {
+              onOptionsChanged(
+                options.copyWith(themeMode: mode),
+              );
+            },
+          ),
+        ],
+      ),
     );
   }
 }
@@ -448,7 +477,7 @@
         padding: const EdgeInsets.only(bottom: 124.0),
         children: <Widget>[
           const _Heading('Display'),
-          _ThemeItem(options, onOptionsChanged),
+          _ThemeModeItem(options, onOptionsChanged),
           _TextScaleFactorItem(options, onOptionsChanged),
           _TextDirectionItem(options, onOptionsChanged),
           _TimeDilationItem(options, onOptionsChanged),
diff --git a/examples/flutter_gallery/lib/gallery/themes.dart b/examples/flutter_gallery/lib/gallery/themes.dart
index e500f95..7843012 100644
--- a/examples/flutter_gallery/lib/gallery/themes.dart
+++ b/examples/flutter_gallery/lib/gallery/themes.dart
@@ -4,15 +4,8 @@
 
 import 'package:flutter/material.dart';
 
-class GalleryTheme {
-  const GalleryTheme._(this.name, this.data);
-
-  final String name;
-  final ThemeData data;
-}
-
-final GalleryTheme kDarkGalleryTheme = GalleryTheme._('Dark', _buildDarkTheme());
-final GalleryTheme kLightGalleryTheme = GalleryTheme._('Light', _buildLightTheme());
+final ThemeData kLightGalleryTheme = _buildLightTheme();
+final ThemeData kDarkGalleryTheme = _buildDarkTheme();
 
 TextTheme _buildTextTheme(TextTheme base) {
   return base.copyWith(
diff --git a/examples/flutter_gallery/test/accessibility_test.dart b/examples/flutter_gallery/test/accessibility_test.dart
index 9fef8bc..2091e8c 100644
--- a/examples/flutter_gallery/test/accessibility_test.dart
+++ b/examples/flutter_gallery/test/accessibility_test.dart
@@ -472,15 +472,15 @@
 
   group('All material demos meet text contrast guidelines', () {
     final List<ThemeData> themes = <ThemeData>[
-      kLightGalleryTheme.data,
+      kLightGalleryTheme,
       ThemeData.light(),
-      // TODO(hansmuller): add kDarkGalleryTheme.data, ThemeData.dark(), see #22044
+      // TODO(hansmuller): add kDarkGalleryTheme, ThemeData.dark(), see #22044
     ];
 
     const List<String> themeNames = <String>[
       'kLightGalleryTheme',
       'ThemeData.light()',
-      // TODO(hansmuller): add 'kDarkGalleryTheme', 'ThemeData.dark()', see 22044
+      // TODO(hansmuller): add 'kDarkGalleryTheme', 'ThemeData.dark()', see #22044
     ];
 
     for (int themeIndex = 0; themeIndex < themes.length; themeIndex += 1) {
diff --git a/examples/flutter_gallery/test/drawer_test.dart b/examples/flutter_gallery/test/drawer_test.dart
index afe4bdc..5a9a614 100644
--- a/examples/flutter_gallery/test/drawer_test.dart
+++ b/examples/flutter_gallery/test/drawer_test.dart
@@ -30,18 +30,40 @@
     await tester.tap(find.byTooltip('Toggle options page'));
     await tester.pumpAndSettle();
 
+    // Verify theme settings
     MaterialApp app = find.byType(MaterialApp).evaluate().first.widget;
     expect(app.theme.brightness, equals(Brightness.light));
+    expect(app.darkTheme.brightness, equals(Brightness.dark));
 
-    // Switch to the dark theme: first switch control
-    await tester.tap(find.byType(Switch).first);
+    // Switch to the dark theme: first menu button, choose 'Dark'
+    await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+    await tester.pumpAndSettle();
+    await tester.tap(find.text('Dark'));
     await tester.pumpAndSettle();
     app = find.byType(MaterialApp).evaluate().first.widget;
-    expect(app.theme.brightness, equals(Brightness.dark));
+    expect(app.themeMode, ThemeMode.dark);
+
+    // Switch to the light theme: first menu button, choose 'Light'
+    await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+    await tester.pumpAndSettle();
+    await tester.tap(find.text('Light'));
+    await tester.pumpAndSettle();
+    app = find.byType(MaterialApp).evaluate().first.widget;
+    expect(app.themeMode, ThemeMode.light);
+
+    // Switch back to system theme setting: first menu button, choose 'System Default'
+    await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+    await tester.pumpAndSettle();
+    await tester.tap(find.text('System Default').at(1));
+    await tester.pumpAndSettle();
+    app = find.byType(MaterialApp).evaluate().first.widget;
+    expect(app.themeMode, ThemeMode.system);
+
+    // Verify platform settings
     expect(app.theme.platform, equals(TargetPlatform.android));
 
-    // Popup the platform menu: second menu button, choose 'Cupertino'
-    await tester.tap(find.byIcon(Icons.arrow_drop_down).at(1));
+    // Popup the platform menu: third menu button, choose 'Cupertino'
+    await tester.tap(find.byIcon(Icons.arrow_drop_down).at(2));
     await tester.pumpAndSettle();
     await tester.tap(find.text('Cupertino').at(1));
     await tester.pumpAndSettle();
@@ -52,8 +74,8 @@
     final Size origTextSize = tester.getSize(find.text('Text size'));
     expect(origTextSize, equals(const Size(144.0, 16.0)));
 
-    // Popup the text size menu: first menu button, choose 'Small'
-    await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+    // Popup the text size menu: second menu button, choose 'Small'
+    await tester.tap(find.byIcon(Icons.arrow_drop_down).at(1));
     await tester.pumpAndSettle();
     await tester.tap(find.text('Small'));
     await tester.pumpAndSettle();
@@ -61,21 +83,21 @@
     expect(textSize, equals(const Size(116.0, 13.0)));
 
     // Set font scale back to the default.
-    await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+    await tester.tap(find.byIcon(Icons.arrow_drop_down).at(1));
     await tester.pumpAndSettle();
-    await tester.tap(find.text('System Default'));
+    await tester.tap(find.text('System Default').at(1));
     await tester.pumpAndSettle();
     textSize = tester.getSize(find.text('Text size'));
     expect(textSize, origTextSize);
 
-    // Switch to slow animation: third switch control
+    // Switch to slow animation: second switch control
     expect(timeDilation, 1.0);
-    await tester.tap(find.byType(Switch).at(2));
+    await tester.tap(find.byType(Switch).at(1));
     await tester.pumpAndSettle();
     expect(timeDilation, greaterThan(1.0));
 
-    // Restore normal animation: third switch control
-    await tester.tap(find.byType(Switch).at(2));
+    // Restore normal animation: second switch control
+    await tester.tap(find.byType(Switch).at(1));
     await tester.pumpAndSettle();
     expect(timeDilation, 1.0);
 
diff --git a/examples/flutter_gallery/test/smoke_test.dart b/examples/flutter_gallery/test/smoke_test.dart
index 9a15506..76a1dfb 100644
--- a/examples/flutter_gallery/test/smoke_test.dart
+++ b/examples/flutter_gallery/test/smoke_test.dart
@@ -109,28 +109,28 @@
   await tester.tap(showOptionsPageButton);
   await tester.pumpAndSettle();
 
-  // Switch to the dark theme: first switch control
+  // Switch to the dark theme: first menu button, choose 'Dark'
+  await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+  await tester.pumpAndSettle();
+  await tester.tap(find.text('Dark'));
+  await tester.pumpAndSettle();
+
+  // Switch back to system theme setting: first menu button, choose 'System Default'
+  await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
+  await tester.pumpAndSettle();
+  await tester.tap(find.text('System Default').at(1));
+  await tester.pumpAndSettle();
+
+  // Switch text direction: first switch
   await tester.tap(find.byType(Switch).first);
   await tester.pumpAndSettle();
 
-  // Switch back to the light theme: first switch control again
+  // Switch back to system text direction: first switch control again
   await tester.tap(find.byType(Switch).first);
   await tester.pumpAndSettle();
 
-  // Popup the text size menu: first menu button, choose 'Small'
-  await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
-  await tester.pumpAndSettle();
-  await tester.tap(find.text('Small'));
-  await tester.pumpAndSettle();
-
-  // Popup the text size menu: first menu button, choose 'Normal'
-  await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
-  await tester.pumpAndSettle();
-  await tester.tap(find.text('Normal'));
-  await tester.pumpAndSettle();
-
   // Scroll the 'Send feedback' item into view
-  await tester.drag(find.text('Normal'), const Offset(0.0, -1000.0));
+  await tester.drag(find.text('Theme'), const Offset(0.0, -1000.0));
   await tester.pumpAndSettle();
   await tester.tap(find.text('Send feedback'));
   await tester.pumpAndSettle();