Unverified Commit b5fff3b2 authored by Darren Austin's avatar Darren Austin Committed by GitHub

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.
parent 11d3d45b
...@@ -62,7 +62,7 @@ class _GalleryAppState extends State<GalleryApp> { ...@@ -62,7 +62,7 @@ class _GalleryAppState extends State<GalleryApp> {
void initState() { void initState() {
super.initState(); super.initState();
_options = GalleryOptions( _options = GalleryOptions(
theme: kLightGalleryTheme, themeMode: ThemeMode.system,
textScaleFactor: kAllGalleryTextScaleValues[0], textScaleFactor: kAllGalleryTextScaleValues[0],
timeDilation: timeDilation, timeDilation: timeDilation,
platform: defaultTargetPlatform, platform: defaultTargetPlatform,
...@@ -134,7 +134,9 @@ class _GalleryAppState extends State<GalleryApp> { ...@@ -134,7 +134,9 @@ class _GalleryAppState extends State<GalleryApp> {
return ScopedModel<AppStateModel>( return ScopedModel<AppStateModel>(
model: model, model: model,
child: MaterialApp( 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', title: 'Flutter Gallery',
color: Colors.grey, color: Colors.grey,
showPerformanceOverlay: _options.showPerformanceOverlay, showPerformanceOverlay: _options.showPerformanceOverlay,
...@@ -148,12 +150,14 @@ class _GalleryAppState extends State<GalleryApp> { ...@@ -148,12 +150,14 @@ class _GalleryAppState extends State<GalleryApp> {
// Specifically use a blank Cupertino theme here and do not transfer // Specifically use a blank Cupertino theme here and do not transfer
// over the Material primary color etc except the brightness to // over the Material primary color etc except the brightness to
// showcase standard iOS looks. // showcase standard iOS looks.
CupertinoTheme( Builder(builder: (BuildContext context) {
data: CupertinoThemeData( return CupertinoTheme(
brightness: _options.theme.data.brightness, data: CupertinoThemeData(
), brightness: Theme.of(context).brightness,
child: child, ),
), child: child,
);
}),
), ),
); );
}, },
......
...@@ -6,11 +6,10 @@ import 'package:flutter/material.dart'; ...@@ -6,11 +6,10 @@ import 'package:flutter/material.dart';
import 'about.dart'; import 'about.dart';
import 'scales.dart'; import 'scales.dart';
import 'themes.dart';
class GalleryOptions { class GalleryOptions {
GalleryOptions({ GalleryOptions({
this.theme, this.themeMode,
this.textScaleFactor, this.textScaleFactor,
this.textDirection = TextDirection.ltr, this.textDirection = TextDirection.ltr,
this.timeDilation = 1.0, this.timeDilation = 1.0,
...@@ -20,7 +19,7 @@ class GalleryOptions { ...@@ -20,7 +19,7 @@ class GalleryOptions {
this.showPerformanceOverlay = false, this.showPerformanceOverlay = false,
}); });
final GalleryTheme theme; final ThemeMode themeMode;
final GalleryTextScaleValue textScaleFactor; final GalleryTextScaleValue textScaleFactor;
final TextDirection textDirection; final TextDirection textDirection;
final double timeDilation; final double timeDilation;
...@@ -30,7 +29,7 @@ class GalleryOptions { ...@@ -30,7 +29,7 @@ class GalleryOptions {
final bool showOffscreenLayersCheckerboard; final bool showOffscreenLayersCheckerboard;
GalleryOptions copyWith({ GalleryOptions copyWith({
GalleryTheme theme, ThemeMode themeMode,
GalleryTextScaleValue textScaleFactor, GalleryTextScaleValue textScaleFactor,
TextDirection textDirection, TextDirection textDirection,
double timeDilation, double timeDilation,
...@@ -40,7 +39,7 @@ class GalleryOptions { ...@@ -40,7 +39,7 @@ class GalleryOptions {
bool showOffscreenLayersCheckerboard, bool showOffscreenLayersCheckerboard,
}) { }) {
return GalleryOptions( return GalleryOptions(
theme: theme ?? this.theme, themeMode: themeMode ?? this.themeMode,
textScaleFactor: textScaleFactor ?? this.textScaleFactor, textScaleFactor: textScaleFactor ?? this.textScaleFactor,
textDirection: textDirection ?? this.textDirection, textDirection: textDirection ?? this.textDirection,
timeDilation: timeDilation ?? this.timeDilation, timeDilation: timeDilation ?? this.timeDilation,
...@@ -56,7 +55,7 @@ class GalleryOptions { ...@@ -56,7 +55,7 @@ class GalleryOptions {
if (runtimeType != other.runtimeType) if (runtimeType != other.runtimeType)
return false; return false;
final GalleryOptions typedOther = other; final GalleryOptions typedOther = other;
return theme == typedOther.theme return themeMode == typedOther.themeMode
&& textScaleFactor == typedOther.textScaleFactor && textScaleFactor == typedOther.textScaleFactor
&& textDirection == typedOther.textDirection && textDirection == typedOther.textDirection
&& platform == typedOther.platform && platform == typedOther.platform
...@@ -67,7 +66,7 @@ class GalleryOptions { ...@@ -67,7 +66,7 @@ class GalleryOptions {
@override @override
int get hashCode => hashValues( int get hashCode => hashValues(
theme, themeMode,
textScaleFactor, textScaleFactor,
textDirection, textDirection,
timeDilation, timeDilation,
...@@ -79,7 +78,7 @@ class GalleryOptions { ...@@ -79,7 +78,7 @@ class GalleryOptions {
@override @override
String toString() { String toString() {
return '$runtimeType($theme)'; return '$runtimeType($themeMode)';
} }
} }
...@@ -202,25 +201,55 @@ class _Heading extends StatelessWidget { ...@@ -202,25 +201,55 @@ class _Heading extends StatelessWidget {
} }
} }
class _ThemeItem extends StatelessWidget { class _ThemeModeItem extends StatelessWidget {
const _ThemeItem(this.options, this.onOptionsChanged); const _ThemeModeItem(this.options, this.onOptionsChanged);
final GalleryOptions options; final GalleryOptions options;
final ValueChanged<GalleryOptions> onOptionsChanged; final ValueChanged<GalleryOptions> onOptionsChanged;
static final Map<ThemeMode, String> modeLabels = <ThemeMode, String>{
ThemeMode.system: 'System Default',
ThemeMode.light: 'Light',
ThemeMode.dark: 'Dark',
};
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _BooleanItem( return _OptionsItem(
'Dark Theme', child: Row(
options.theme == kDarkGalleryTheme, children: <Widget>[
(bool value) { Expanded(
onOptionsChanged( child: Column(
options.copyWith( crossAxisAlignment: CrossAxisAlignment.start,
theme: value ? kDarkGalleryTheme : kLightGalleryTheme, children: <Widget>[
const Text('Theme'),
Text(
'${modeLabels[options.themeMode]}',
style: Theme.of(context).primaryTextTheme.body1,
),
],
),
), ),
); PopupMenuButton<ThemeMode>(
}, padding: const EdgeInsetsDirectional.only(end: 16.0),
switchKey: const Key('dark_theme'), 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 @@ class GalleryOptionsPage extends StatelessWidget { ...@@ -448,7 +477,7 @@ class GalleryOptionsPage extends StatelessWidget {
padding: const EdgeInsets.only(bottom: 124.0), padding: const EdgeInsets.only(bottom: 124.0),
children: <Widget>[ children: <Widget>[
const _Heading('Display'), const _Heading('Display'),
_ThemeItem(options, onOptionsChanged), _ThemeModeItem(options, onOptionsChanged),
_TextScaleFactorItem(options, onOptionsChanged), _TextScaleFactorItem(options, onOptionsChanged),
_TextDirectionItem(options, onOptionsChanged), _TextDirectionItem(options, onOptionsChanged),
_TimeDilationItem(options, onOptionsChanged), _TimeDilationItem(options, onOptionsChanged),
......
...@@ -4,15 +4,8 @@ ...@@ -4,15 +4,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class GalleryTheme { final ThemeData kLightGalleryTheme = _buildLightTheme();
const GalleryTheme._(this.name, this.data); final ThemeData kDarkGalleryTheme = _buildDarkTheme();
final String name;
final ThemeData data;
}
final GalleryTheme kDarkGalleryTheme = GalleryTheme._('Dark', _buildDarkTheme());
final GalleryTheme kLightGalleryTheme = GalleryTheme._('Light', _buildLightTheme());
TextTheme _buildTextTheme(TextTheme base) { TextTheme _buildTextTheme(TextTheme base) {
return base.copyWith( return base.copyWith(
......
...@@ -472,15 +472,15 @@ void main() { ...@@ -472,15 +472,15 @@ void main() {
group('All material demos meet text contrast guidelines', () { group('All material demos meet text contrast guidelines', () {
final List<ThemeData> themes = <ThemeData>[ final List<ThemeData> themes = <ThemeData>[
kLightGalleryTheme.data, kLightGalleryTheme,
ThemeData.light(), ThemeData.light(),
// TODO(hansmuller): add kDarkGalleryTheme.data, ThemeData.dark(), see #22044 // TODO(hansmuller): add kDarkGalleryTheme, ThemeData.dark(), see #22044
]; ];
const List<String> themeNames = <String>[ const List<String> themeNames = <String>[
'kLightGalleryTheme', 'kLightGalleryTheme',
'ThemeData.light()', '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) { for (int themeIndex = 0; themeIndex < themes.length; themeIndex += 1) {
......
...@@ -30,18 +30,40 @@ void main() { ...@@ -30,18 +30,40 @@ void main() {
await tester.tap(find.byTooltip('Toggle options page')); await tester.tap(find.byTooltip('Toggle options page'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Verify theme settings
MaterialApp app = find.byType(MaterialApp).evaluate().first.widget; MaterialApp app = find.byType(MaterialApp).evaluate().first.widget;
expect(app.theme.brightness, equals(Brightness.light)); expect(app.theme.brightness, equals(Brightness.light));
expect(app.darkTheme.brightness, equals(Brightness.dark));
// Switch to the dark theme: first switch control // Switch to the dark theme: first menu button, choose 'Dark'
await tester.tap(find.byType(Switch).first); 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.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(); await tester.pumpAndSettle();
app = find.byType(MaterialApp).evaluate().first.widget; app = find.byType(MaterialApp).evaluate().first.widget;
expect(app.theme.brightness, equals(Brightness.dark)); expect(app.themeMode, ThemeMode.system);
// Verify platform settings
expect(app.theme.platform, equals(TargetPlatform.android)); expect(app.theme.platform, equals(TargetPlatform.android));
// Popup the platform menu: second menu button, choose 'Cupertino' // Popup the platform menu: third menu button, choose 'Cupertino'
await tester.tap(find.byIcon(Icons.arrow_drop_down).at(1)); await tester.tap(find.byIcon(Icons.arrow_drop_down).at(2));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.tap(find.text('Cupertino').at(1)); await tester.tap(find.text('Cupertino').at(1));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -52,8 +74,8 @@ void main() { ...@@ -52,8 +74,8 @@ void main() {
final Size origTextSize = tester.getSize(find.text('Text size')); final Size origTextSize = tester.getSize(find.text('Text size'));
expect(origTextSize, equals(const Size(144.0, 16.0))); expect(origTextSize, equals(const Size(144.0, 16.0)));
// Popup the text size menu: first menu button, choose 'Small' // Popup the text size menu: second menu button, choose 'Small'
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.pumpAndSettle();
await tester.tap(find.text('Small')); await tester.tap(find.text('Small'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -61,21 +83,21 @@ void main() { ...@@ -61,21 +83,21 @@ void main() {
expect(textSize, equals(const Size(116.0, 13.0))); expect(textSize, equals(const Size(116.0, 13.0)));
// Set font scale back to the default. // 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.pumpAndSettle();
await tester.tap(find.text('System Default')); await tester.tap(find.text('System Default').at(1));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
textSize = tester.getSize(find.text('Text size')); textSize = tester.getSize(find.text('Text size'));
expect(textSize, origTextSize); expect(textSize, origTextSize);
// Switch to slow animation: third switch control // Switch to slow animation: second switch control
expect(timeDilation, 1.0); expect(timeDilation, 1.0);
await tester.tap(find.byType(Switch).at(2)); await tester.tap(find.byType(Switch).at(1));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(timeDilation, greaterThan(1.0)); expect(timeDilation, greaterThan(1.0));
// Restore normal animation: third switch control // Restore normal animation: second switch control
await tester.tap(find.byType(Switch).at(2)); await tester.tap(find.byType(Switch).at(1));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(timeDilation, 1.0); expect(timeDilation, 1.0);
......
...@@ -109,28 +109,28 @@ Future<void> smokeOptionsPage(WidgetTester tester) async { ...@@ -109,28 +109,28 @@ Future<void> smokeOptionsPage(WidgetTester tester) async {
await tester.tap(showOptionsPageButton); await tester.tap(showOptionsPageButton);
await tester.pumpAndSettle(); 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.byType(Switch).first); await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.tap(find.text('Dark'));
// Switch back to the light theme: first switch control again
await tester.tap(find.byType(Switch).first);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Popup the text size menu: first menu button, choose 'Small' // Switch back to system theme setting: first menu button, choose 'System Default'
await tester.tap(find.byIcon(Icons.arrow_drop_down).first); await tester.tap(find.byIcon(Icons.arrow_drop_down).first);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.tap(find.text('Small')); await tester.tap(find.text('System Default').at(1));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Popup the text size menu: first menu button, choose 'Normal' // Switch text direction: first switch
await tester.tap(find.byIcon(Icons.arrow_drop_down).first); await tester.tap(find.byType(Switch).first);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.tap(find.text('Normal'));
// Switch back to system text direction: first switch control again
await tester.tap(find.byType(Switch).first);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scroll the 'Send feedback' item into view // 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.pumpAndSettle();
await tester.tap(find.text('Send feedback')); await tester.tap(find.text('Send feedback'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment