mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Added MaterialApp.themeMode to control which theme is used. (#35499)
Added support for a themeMode property to the MaterialApp to control how the light or dark theme is selected.
This commit is contained in:
parent
42a01befa0
commit
af1bd09c78
|
@ -34,6 +34,19 @@ const TextStyle _errorTextStyle = TextStyle(
|
|||
debugLabel: 'fallback style; consider putting your text in a Material',
|
||||
);
|
||||
|
||||
/// Describes which theme will be used by [MaterialApp].
|
||||
enum ThemeMode {
|
||||
/// Use either the light or dark theme based on what the user has selected in
|
||||
/// the system settings.
|
||||
system,
|
||||
|
||||
/// Always use the light mode regardless of system preference.
|
||||
light,
|
||||
|
||||
/// Always use the dark mode (if available) regardless of system preference.
|
||||
dark,
|
||||
}
|
||||
|
||||
/// An application that uses material design.
|
||||
///
|
||||
/// A convenience widget that wraps a number of widgets that are commonly
|
||||
|
@ -99,6 +112,7 @@ class MaterialApp extends StatefulWidget {
|
|||
this.color,
|
||||
this.theme,
|
||||
this.darkTheme,
|
||||
this.themeMode = ThemeMode.system,
|
||||
this.locale,
|
||||
this.localizationsDelegates,
|
||||
this.localeListResolutionCallback,
|
||||
|
@ -169,13 +183,15 @@ class MaterialApp extends StatefulWidget {
|
|||
/// Default visual properties, like colors fonts and shapes, for this app's
|
||||
/// material widgets.
|
||||
///
|
||||
/// A second [darkTheme] [ThemeData] value, which is used when the underlying
|
||||
/// platform requests a "dark mode" UI, can also be specified.
|
||||
/// A second [darkTheme] [ThemeData] value, which is used to provide a dark
|
||||
/// version of the user interface can also be specified. [themeMode] will
|
||||
/// control which theme will be used if a [darkTheme] is provided.
|
||||
///
|
||||
/// The default value of this property is the value of [ThemeData.light()].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [themeMode], which controls which theme to use.
|
||||
/// * [MediaQueryData.platformBrightness], which indicates the platform's
|
||||
/// desired brightness and is used to automatically toggle between [theme]
|
||||
/// and [darkTheme] in [MaterialApp].
|
||||
|
@ -183,20 +199,21 @@ class MaterialApp extends StatefulWidget {
|
|||
/// colors.
|
||||
final ThemeData theme;
|
||||
|
||||
/// The [ThemeData] to use when the platform specifically requests a dark
|
||||
/// themed UI.
|
||||
/// The [ThemeData] to use when a 'dark mode' is requested by the system.
|
||||
///
|
||||
/// Host platforms such as Android Pie can request a system-wide "dark mode"
|
||||
/// when entering battery saver mode.
|
||||
/// Some host platforms allow the users to select a system-wide 'dark mode',
|
||||
/// or the application may want to offer the user the ability to choose a
|
||||
/// dark theme just for this application. This is theme that will be used for
|
||||
/// such cases. [themeMode] will control which theme will be used.
|
||||
///
|
||||
/// When the host platform requests a [Brightness.dark] mode, you may want to
|
||||
/// supply a [ThemeData.brightness] that's also [Brightness.dark].
|
||||
/// This theme should have a [ThemeData.brightness] set to [Brightness.dark].
|
||||
///
|
||||
/// Uses [theme] instead when null. Defaults to the value of
|
||||
/// [ThemeData.light()] when both [darkTheme] and [theme] are null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [themeMode], which controls which theme to use.
|
||||
/// * [MediaQueryData.platformBrightness], which indicates the platform's
|
||||
/// desired brightness and is used to automatically toggle between [theme]
|
||||
/// and [darkTheme] in [MaterialApp].
|
||||
|
@ -204,6 +221,32 @@ class MaterialApp extends StatefulWidget {
|
|||
/// [MediaQueryData.platformBrightness].
|
||||
final ThemeData darkTheme;
|
||||
|
||||
/// Determines which theme will be used by the application if both [theme]
|
||||
/// and [darkTheme] are provided.
|
||||
///
|
||||
/// If set to [ThemeMode.system], the choice of which theme to use will
|
||||
/// be based on the user's system preferences. If the [MediaQuery.platformBrightnessOf]
|
||||
/// is [Brightness.light], [theme] will be used. If it is [Brightness.dark],
|
||||
/// [darkTheme] will be used (unless it is [null], in which case [theme]
|
||||
/// will be used.
|
||||
///
|
||||
/// If set to [ThemeMode.light] the [theme] will always be used,
|
||||
/// regardless of the user's system preference.
|
||||
///
|
||||
/// If set to [ThemeMode.dark] the [darkTheme] will be used
|
||||
/// regardless of the user's system preference. If [darkTheme] is [null]
|
||||
/// then it will fallback to using [theme].
|
||||
///
|
||||
/// The default value is [ThemeMode.system].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [theme], which is used when a light mode is selected.
|
||||
/// * [darkTheme], which is used when a dark mode is selected.
|
||||
/// * [ThemeData.brightness], which indicates to various parts of the
|
||||
/// system what kind of theme is being used.
|
||||
final ThemeMode themeMode;
|
||||
|
||||
/// {@macro flutter.widgets.widgetsApp.color}
|
||||
final Color color;
|
||||
|
||||
|
@ -454,15 +497,16 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||
onUnknownRoute: widget.onUnknownRoute,
|
||||
builder: (BuildContext context, Widget child) {
|
||||
// Use a light theme, dark theme, or fallback theme.
|
||||
final ThemeMode mode = widget.themeMode ?? ThemeMode.system;
|
||||
ThemeData theme;
|
||||
final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
|
||||
if (platformBrightness == ui.Brightness.dark && widget.darkTheme != null) {
|
||||
theme = widget.darkTheme;
|
||||
} else if (widget.theme != null) {
|
||||
theme = widget.theme;
|
||||
} else {
|
||||
theme = ThemeData.fallback();
|
||||
if (widget.darkTheme != null) {
|
||||
final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
|
||||
if (mode == ThemeMode.dark ||
|
||||
(mode == ThemeMode.system && platformBrightness == ui.Brightness.dark)) {
|
||||
theme = widget.darkTheme;
|
||||
}
|
||||
}
|
||||
theme ??= widget.theme ?? ThemeData.fallback();
|
||||
|
||||
return AnimatedTheme(
|
||||
data: theme,
|
||||
|
|
|
@ -447,7 +447,99 @@ void main() {
|
|||
expect(find.text('Select All'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp uses regular theme when platformBrightness is light', (WidgetTester tester) async {
|
||||
testWidgets('MaterialApp uses regular theme when themeMode is light', (WidgetTester tester) async {
|
||||
// Mock the Window to explicitly report a light platformBrightness.
|
||||
tester.binding.window.platformBrightnessTestValue = Brightness.light;
|
||||
|
||||
ThemeData appliedTheme;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: ThemeMode.light,
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
appliedTheme = Theme.of(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(appliedTheme.brightness, Brightness.light);
|
||||
|
||||
// Mock the Window to explicitly report a dark platformBrightness.
|
||||
tester.binding.window.platformBrightnessTestValue = Brightness.dark;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: ThemeMode.light,
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
appliedTheme = Theme.of(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(appliedTheme.brightness, Brightness.light);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp uses darkTheme when themeMode is dark', (WidgetTester tester) async {
|
||||
// Mock the Window to explicitly report a light platformBrightness.
|
||||
tester.binding.window.platformBrightnessTestValue = Brightness.light;
|
||||
|
||||
ThemeData appliedTheme;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: ThemeMode.dark,
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
appliedTheme = Theme.of(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(appliedTheme.brightness, Brightness.dark);
|
||||
|
||||
// Mock the Window to explicitly report a dark platformBrightness.
|
||||
tester.binding.window.platformBrightnessTestValue = Brightness.dark;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: ThemeMode.dark,
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
appliedTheme = Theme.of(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(appliedTheme.brightness, Brightness.dark);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp uses regular theme when themeMode is system and platformBrightness is light', (WidgetTester tester) async {
|
||||
// Mock the Window to explicitly report a light platformBrightness.
|
||||
final TestWidgetsFlutterBinding binding = tester.binding;
|
||||
binding.window.platformBrightnessTestValue = Brightness.light;
|
||||
|
@ -462,6 +554,7 @@ void main() {
|
|||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: ThemeMode.system,
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
appliedTheme = Theme.of(context);
|
||||
|
@ -474,6 +567,31 @@ void main() {
|
|||
expect(appliedTheme.brightness, Brightness.light);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp uses darkTheme when themeMode is system and platformBrightness is dark', (WidgetTester tester) async {
|
||||
// Mock the Window to explicitly report a dark platformBrightness.
|
||||
tester.binding.window.platformBrightnessTestValue = Brightness.dark;
|
||||
|
||||
ThemeData appliedTheme;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: ThemeMode.system,
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
appliedTheme = Theme.of(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(appliedTheme.brightness, Brightness.dark);
|
||||
});
|
||||
|
||||
testWidgets('MaterialApp uses light theme when platformBrightness is dark but no dark theme is provided', (WidgetTester tester) async {
|
||||
// Mock the Window to explicitly report a dark platformBrightness.
|
||||
final TestWidgetsFlutterBinding binding = tester.binding;
|
||||
|
|
Loading…
Reference in a new issue