Unverified Commit a13fdbcf authored by Matt Carroll's avatar Matt Carroll Committed by GitHub

Implemented Dark Mode for Android (#25525) (#26605)

parent 0aee2f50
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -96,6 +98,7 @@ class MaterialApp extends StatefulWidget { ...@@ -96,6 +98,7 @@ class MaterialApp extends StatefulWidget {
this.onGenerateTitle, this.onGenerateTitle,
this.color, this.color,
this.theme, this.theme,
this.darkTheme,
this.locale, this.locale,
this.localizationsDelegates, this.localizationsDelegates,
this.localeListResolutionCallback, this.localeListResolutionCallback,
...@@ -163,9 +166,44 @@ class MaterialApp extends StatefulWidget { ...@@ -163,9 +166,44 @@ class MaterialApp extends StatefulWidget {
/// This value is passed unmodified to [WidgetsApp.onGenerateTitle]. /// This value is passed unmodified to [WidgetsApp.onGenerateTitle].
final GenerateAppTitle onGenerateTitle; final GenerateAppTitle onGenerateTitle;
/// The colors to use for the application's widgets. /// 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.
///
/// The default value of this property is the value of [ThemeData.light()].
///
/// See also:
///
/// * [MediaQueryData.platformBrightness], which indicates the platform's
/// desired brightness and is used to automatically toggle between [theme]
/// and [darkTheme] in [MaterialApp].
/// * [ThemeData.brightness], which indicates the [Brightness] of a theme's
/// colors.
final ThemeData theme; final ThemeData theme;
/// The [ThemeData] to use when the platform specifically requests a dark
/// themed UI.
///
/// Host platforms such as Android Pie can request a system-wide "dark mode"
/// when entering battery saver mode.
///
/// When the host platform requests a [Brightness.dark] mode, you may want to
/// supply a [ThemeData.brightness] that's also [Brightness.dark].
///
/// Uses [theme] instead when null. Defaults to the value of
/// [ThemeData.light()] when both [darkTheme] and [theme] are null.
///
/// See also:
///
/// * [MediaQueryData.platformBrightness], which indicates the platform's
/// desired brightness and is used to automatically toggle between [theme]
/// and [darkTheme] in [MaterialApp].
/// * [ThemeData.brightness], which is typically set to the value of
/// [MediaQueryData.platformBrightness].
final ThemeData darkTheme;
/// {@macro flutter.widgets.widgetsApp.color} /// {@macro flutter.widgets.widgetsApp.color}
final Color color; final Color color;
...@@ -403,45 +441,80 @@ class _MaterialAppState extends State<MaterialApp> { ...@@ -403,45 +441,80 @@ class _MaterialAppState extends State<MaterialApp> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = widget.theme ?? ThemeData.fallback(); Widget result = WidgetsApp(
Widget result = AnimatedTheme( key: GlobalObjectKey(this),
data: theme, navigatorKey: widget.navigatorKey,
isMaterialAppTheme: true, navigatorObservers: _navigatorObservers,
child: WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) =>
MaterialPageRoute<T>(settings: settings, builder: builder), MaterialPageRoute<T>(settings: settings, builder: builder),
home: widget.home, home: widget.home,
routes: widget.routes, routes: widget.routes,
initialRoute: widget.initialRoute, initialRoute: widget.initialRoute,
onGenerateRoute: widget.onGenerateRoute, onGenerateRoute: widget.onGenerateRoute,
onUnknownRoute: widget.onUnknownRoute, onUnknownRoute: widget.onUnknownRoute,
builder: widget.builder, builder: (BuildContext context, Widget child) {
title: widget.title, // Use a light theme, dark theme, or fallback theme.
onGenerateTitle: widget.onGenerateTitle, ThemeData theme;
textStyle: _errorTextStyle, final ui.Brightness platformBrightness = MediaQuery.platformBrightnessOf(context);
// blue is the primary color of the default theme if (platformBrightness == ui.Brightness.dark && widget.darkTheme != null) {
color: widget.color ?? theme?.primaryColor ?? Colors.blue, theme = widget.darkTheme;
locale: widget.locale, } else if (widget.theme != null) {
localizationsDelegates: _localizationsDelegates, theme = widget.theme;
localeResolutionCallback: widget.localeResolutionCallback, } else {
localeListResolutionCallback: widget.localeListResolutionCallback, theme = ThemeData.fallback();
supportedLocales: widget.supportedLocales, }
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, return AnimatedTheme(
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, data: theme,
showSemanticsDebugger: widget.showSemanticsDebugger, isMaterialAppTheme: true,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, child: widget.builder != null
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { ? Builder(
return FloatingActionButton( builder: (BuildContext context) {
child: const Icon(Icons.search), // Why are we surrounding a builder with a builder?
onPressed: onPressed, //
mini: true, // The widget.builder may contain code that invokes
); // Theme.of(), which should return the theme we selected
}, // above in AnimatedTheme. However, if we invoke
), // widget.builder() directly as the child of AnimatedTheme
// then there is no Context separating them, and the
// widget.builder() will not find the theme. Therefore, we
// surround widget.builder with yet another builder so that
// a context separates them and Theme.of() correctly
// resolves to the theme we passed to AnimatedTheme.
return widget.builder(context, child);
},
)
: child,
);
},
title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: _errorTextStyle,
// The color property is always pulled from the light theme, even if dark
// mode is activated. This was done to simplify the technical details
// of switching themes and it was deemed acceptable because this color
// property is only used on old Android OSes to color the app bar in
// Android's switcher UI.
//
// blue is the primary color of the default theme
color: widget.color ?? widget.theme?.primaryColor ?? Colors.blue,
locale: widget.locale,
localizationsDelegates: _localizationsDelegates,
localeResolutionCallback: widget.localeResolutionCallback,
localeListResolutionCallback: widget.localeListResolutionCallback,
supportedLocales: widget.supportedLocales,
showPerformanceOverlay: widget.showPerformanceOverlay,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
showSemanticsDebugger: widget.showSemanticsDebugger,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
return FloatingActionButton(
child: const Icon(Icons.search),
onPressed: onPressed,
mini: true,
);
},
); );
assert(() { assert(() {
......
...@@ -33,6 +33,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture ...@@ -33,6 +33,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
window window
..onMetricsChanged = handleMetricsChanged ..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged ..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction; ..onSemanticsAction = _handleSemanticsAction;
initRenderView(); initRenderView();
...@@ -168,6 +169,38 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture ...@@ -168,6 +169,38 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
@protected @protected
void handleTextScaleFactorChanged() { } void handleTextScaleFactorChanged() { }
/// {@template on_platform_brightness_change}
/// Called when the platform brightness changes.
///
/// The current platform brightness can be queried either from a Flutter
/// binding, or from a [MediaQuery] widget.
///
/// ## Sample Code
///
/// Querying [Window.platformBrightness]:
///
/// ```dart
/// final Brightness brightness = WidgetsBinding.instance.window.platformBrightness;
/// ```
///
/// Querying [MediaQuery] directly:
///
/// ```dart
/// final Brightness brightness = MediaQuery.platformBrightnessOf(context);
/// ```
///
/// Querying [MediaQueryData]:
///
/// ```dart
/// final MediaQueryData mediaQueryData = MediaQuery.of(context);
/// final Brightness brightness = mediaQueryData.platformBrightness;
/// ```
///
/// See [Window.onPlatformBrightnessChanged].
/// {@endtemplate}
@protected
void handlePlatformBrightnessChanged() { }
/// Returns a [ViewConfiguration] configured for the [RenderView] based on the /// Returns a [ViewConfiguration] configured for the [RenderView] based on the
/// current environment. /// current environment.
/// ///
......
...@@ -1020,6 +1020,15 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv ...@@ -1020,6 +1020,15 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
}); });
} }
// RENDERING
@override
void didChangePlatformBrightness() {
setState(() {
// The platformBrightness property of window has changed. We reference
// window in our build function, so we need to call setState(), but
// we don't need to cache anything locally.
});
}
// BUILDER // BUILDER
......
...@@ -213,6 +213,9 @@ abstract class WidgetsBindingObserver { ...@@ -213,6 +213,9 @@ abstract class WidgetsBindingObserver {
/// boilerplate. /// boilerplate.
void didChangeTextScaleFactor() { } void didChangeTextScaleFactor() { }
/// {@macro on_platform_brightness_change}
void didChangePlatformBrightness() { }
/// Called when the system tells the app that the user's locale has /// Called when the system tells the app that the user's locale has
/// changed. For example, if the user changes the system language /// changed. For example, if the user changes the system language
/// settings. /// settings.
...@@ -408,6 +411,13 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB ...@@ -408,6 +411,13 @@ mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererB
observer.didChangeTextScaleFactor(); observer.didChangeTextScaleFactor();
} }
@override
void handlePlatformBrightnessChanged() {
super.handlePlatformBrightnessChanged();
for (WidgetsBindingObserver observer in _observers)
observer.didChangePlatformBrightness();
}
@override @override
void handleAccessibilityFeaturesChanged() { void handleAccessibilityFeaturesChanged() {
super.handleAccessibilityFeaturesChanged(); super.handleAccessibilityFeaturesChanged();
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:ui' show Brightness;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -59,6 +60,7 @@ class MediaQueryData { ...@@ -59,6 +60,7 @@ class MediaQueryData {
this.size = Size.zero, this.size = Size.zero,
this.devicePixelRatio = 1.0, this.devicePixelRatio = 1.0,
this.textScaleFactor = 1.0, this.textScaleFactor = 1.0,
this.platformBrightness = Brightness.light,
this.padding = EdgeInsets.zero, this.padding = EdgeInsets.zero,
this.viewInsets = EdgeInsets.zero, this.viewInsets = EdgeInsets.zero,
this.alwaysUse24HourFormat = false, this.alwaysUse24HourFormat = false,
...@@ -78,6 +80,7 @@ class MediaQueryData { ...@@ -78,6 +80,7 @@ class MediaQueryData {
: size = window.physicalSize / window.devicePixelRatio, : size = window.physicalSize / window.devicePixelRatio,
devicePixelRatio = window.devicePixelRatio, devicePixelRatio = window.devicePixelRatio,
textScaleFactor = window.textScaleFactor, textScaleFactor = window.textScaleFactor,
platformBrightness = window.platformBrightness,
padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio), padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio), viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
accessibleNavigation = window.accessibilityFeatures.accessibleNavigation, accessibleNavigation = window.accessibilityFeatures.accessibleNavigation,
...@@ -110,6 +113,15 @@ class MediaQueryData { ...@@ -110,6 +113,15 @@ class MediaQueryData {
/// textScaleFactor defined for a [BuildContext]. /// textScaleFactor defined for a [BuildContext].
final double textScaleFactor; final double textScaleFactor;
/// The current brightness mode of the host platform.
///
/// For example, starting in Android Pie, battery saver mode asks all apps to
/// render in a "dark mode".
///
/// Not all platforms necessarily support a concept of brightness mode. Those
/// platforms will report [Brightness.light] in this property.
final Brightness platformBrightness;
/// The parts of the display that are completely obscured by system UI, /// The parts of the display that are completely obscured by system UI,
/// typically by the device's keyboard. /// typically by the device's keyboard.
/// ///
...@@ -204,6 +216,7 @@ class MediaQueryData { ...@@ -204,6 +216,7 @@ class MediaQueryData {
Size size, Size size,
double devicePixelRatio, double devicePixelRatio,
double textScaleFactor, double textScaleFactor,
Brightness platformBrightness,
EdgeInsets padding, EdgeInsets padding,
EdgeInsets viewInsets, EdgeInsets viewInsets,
bool alwaysUse24HourFormat, bool alwaysUse24HourFormat,
...@@ -216,6 +229,7 @@ class MediaQueryData { ...@@ -216,6 +229,7 @@ class MediaQueryData {
size: size ?? this.size, size: size ?? this.size,
devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
textScaleFactor: textScaleFactor ?? this.textScaleFactor, textScaleFactor: textScaleFactor ?? this.textScaleFactor,
platformBrightness: platformBrightness ?? this.platformBrightness,
padding: padding ?? this.padding, padding: padding ?? this.padding,
viewInsets: viewInsets ?? this.viewInsets, viewInsets: viewInsets ?? this.viewInsets,
alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat, alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat,
...@@ -252,6 +266,7 @@ class MediaQueryData { ...@@ -252,6 +266,7 @@ class MediaQueryData {
size: size, size: size,
devicePixelRatio: devicePixelRatio, devicePixelRatio: devicePixelRatio,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
platformBrightness: platformBrightness,
padding: padding.copyWith( padding: padding.copyWith(
left: removeLeft ? 0.0 : null, left: removeLeft ? 0.0 : null,
top: removeTop ? 0.0 : null, top: removeTop ? 0.0 : null,
...@@ -291,6 +306,7 @@ class MediaQueryData { ...@@ -291,6 +306,7 @@ class MediaQueryData {
size: size, size: size,
devicePixelRatio: devicePixelRatio, devicePixelRatio: devicePixelRatio,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
platformBrightness: platformBrightness,
padding: padding, padding: padding,
viewInsets: viewInsets.copyWith( viewInsets: viewInsets.copyWith(
left: removeLeft ? 0.0 : null, left: removeLeft ? 0.0 : null,
...@@ -314,6 +330,7 @@ class MediaQueryData { ...@@ -314,6 +330,7 @@ class MediaQueryData {
return typedOther.size == size return typedOther.size == size
&& typedOther.devicePixelRatio == devicePixelRatio && typedOther.devicePixelRatio == devicePixelRatio
&& typedOther.textScaleFactor == textScaleFactor && typedOther.textScaleFactor == textScaleFactor
&& typedOther.platformBrightness == platformBrightness
&& typedOther.padding == padding && typedOther.padding == padding
&& typedOther.viewInsets == viewInsets && typedOther.viewInsets == viewInsets
&& typedOther.alwaysUse24HourFormat == alwaysUse24HourFormat && typedOther.alwaysUse24HourFormat == alwaysUse24HourFormat
...@@ -329,6 +346,7 @@ class MediaQueryData { ...@@ -329,6 +346,7 @@ class MediaQueryData {
size, size,
devicePixelRatio, devicePixelRatio,
textScaleFactor, textScaleFactor,
platformBrightness,
padding, padding,
viewInsets, viewInsets,
alwaysUse24HourFormat, alwaysUse24HourFormat,
...@@ -345,6 +363,7 @@ class MediaQueryData { ...@@ -345,6 +363,7 @@ class MediaQueryData {
'size: $size, ' 'size: $size, '
'devicePixelRatio: ${devicePixelRatio.toStringAsFixed(1)}, ' 'devicePixelRatio: ${devicePixelRatio.toStringAsFixed(1)}, '
'textScaleFactor: ${textScaleFactor.toStringAsFixed(1)}, ' 'textScaleFactor: ${textScaleFactor.toStringAsFixed(1)}, '
'platformBrightness: $platformBrightness, '
'padding: $padding, ' 'padding: $padding, '
'viewInsets: $viewInsets, ' 'viewInsets: $viewInsets, '
'alwaysUse24HourFormat: $alwaysUse24HourFormat, ' 'alwaysUse24HourFormat: $alwaysUse24HourFormat, '
...@@ -523,6 +542,15 @@ class MediaQuery extends InheritedWidget { ...@@ -523,6 +542,15 @@ class MediaQuery extends InheritedWidget {
return MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0; return MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0;
} }
/// Returns platformBrightness for the nearest MediaQuery ancestor or
/// [Brightness.light], if no such ancestor exists.
///
/// Use of this method will cause the given [context] to rebuild any time that
/// any property of the ancestor [MediaQuery] changes.
static Brightness platformBrightnessOf(BuildContext context) {
return MediaQuery.of(context, nullOk: true)?.platformBrightness ?? Brightness.light;
}
/// Returns the boldText accessibility setting for the nearest MediaQuery /// Returns the boldText accessibility setting for the nearest MediaQuery
/// ancestor, or false if no such ancestor exists. /// ancestor, or false if no such ancestor exists.
static bool boldTextOverride(BuildContext context) { static bool boldTextOverride(BuildContext context) {
......
...@@ -435,4 +435,165 @@ void main() { ...@@ -435,4 +435,165 @@ void main() {
// Default Cupertino US "select all" text. // Default Cupertino US "select all" text.
expect(find.text('Select All'), findsOneWidget); expect(find.text('Select All'), findsOneWidget);
}); });
testWidgets('MaterialApp uses regular theme when 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;
ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
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;
binding.window.platformBrightnessTestValue = Brightness.dark;
ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses fallback light theme when platformBrightness is dark but no theme is provided at all', (WidgetTester tester) async {
// Mock the Window to explicitly report a dark platformBrightness.
final TestWidgetsFlutterBinding binding = tester.binding;
binding.window.platformBrightnessTestValue = Brightness.dark;
ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses fallback light theme when platformBrightness is light and a dark theme is provided', (WidgetTester tester) async {
// Mock the Window to explicitly report a dark platformBrightness.
final TestWidgetsFlutterBinding binding = tester.binding;
binding.window.platformBrightnessTestValue = Brightness.light;
ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.light);
});
testWidgets('MaterialApp uses dark theme when platformBrightness is dark', (WidgetTester tester) async {
// Mock the Window to explicitly report a dark platformBrightness.
final TestWidgetsFlutterBinding binding = tester.binding;
binding.window.platformBrightnessTestValue = Brightness.dark;
ThemeData appliedTheme;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
appliedTheme = Theme.of(context);
return const SizedBox();
},
),
),
);
expect(appliedTheme.brightness, Brightness.dark);
});
testWidgets('MaterialApp switches themes when the Window platformBrightness changes.', (WidgetTester tester) async {
// Mock the Window to explicitly report a light platformBrightness.
final TestWidgetsFlutterBinding binding = tester.binding;
binding.window.platformBrightnessTestValue = Brightness.light;
ThemeData themeBeforeBrightnessChange;
ThemeData themeAfterBrightnessChange;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
brightness: Brightness.light
),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
home: Builder(
builder: (BuildContext context) {
if (themeBeforeBrightnessChange == null) {
themeBeforeBrightnessChange = Theme.of(context);
} else {
themeAfterBrightnessChange = Theme.of(context);
}
return const SizedBox();
},
),
),
);
// Switch the platformBrightness from light to dark and pump the widget tree
// to process changes.
binding.window.platformBrightnessTestValue = Brightness.dark;
await tester.pumpAndSettle();
expect(themeBeforeBrightnessChange.brightness, Brightness.light);
expect(themeAfterBrightnessChange.brightness, Brightness.dark);
});
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show Brightness;
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -45,6 +46,7 @@ void main() { ...@@ -45,6 +46,7 @@ void main() {
expect(data.invertColors, false); expect(data.invertColors, false);
expect(data.disableAnimations, false); expect(data.disableAnimations, false);
expect(data.boldText, false); expect(data.boldText, false);
expect(data.platformBrightness, Brightness.light);
}); });
testWidgets('MediaQueryData.copyWith defaults to source', (WidgetTester tester) async { testWidgets('MediaQueryData.copyWith defaults to source', (WidgetTester tester) async {
...@@ -60,6 +62,7 @@ void main() { ...@@ -60,6 +62,7 @@ void main() {
expect(copied.invertColors, data.invertColors); expect(copied.invertColors, data.invertColors);
expect(copied.disableAnimations, data.disableAnimations); expect(copied.disableAnimations, data.disableAnimations);
expect(copied.boldText, data.boldText); expect(copied.boldText, data.boldText);
expect(copied.platformBrightness, data.platformBrightness);
}); });
testWidgets('MediaQuery.copyWith copies specified values', (WidgetTester tester) async { testWidgets('MediaQuery.copyWith copies specified values', (WidgetTester tester) async {
...@@ -75,6 +78,7 @@ void main() { ...@@ -75,6 +78,7 @@ void main() {
invertColors: true, invertColors: true,
disableAnimations: true, disableAnimations: true,
boldText: true, boldText: true,
platformBrightness: Brightness.dark,
); );
expect(copied.size, const Size(3.14, 2.72)); expect(copied.size, const Size(3.14, 2.72));
expect(copied.devicePixelRatio, 1.41); expect(copied.devicePixelRatio, 1.41);
...@@ -86,6 +90,7 @@ void main() { ...@@ -86,6 +90,7 @@ void main() {
expect(copied.invertColors, true); expect(copied.invertColors, true);
expect(copied.disableAnimations, true); expect(copied.disableAnimations, true);
expect(copied.boldText, true); expect(copied.boldText, true);
expect(copied.platformBrightness, Brightness.dark);
}); });
testWidgets('MediaQuery.removePadding removes specified padding', (WidgetTester tester) async { testWidgets('MediaQuery.removePadding removes specified padding', (WidgetTester tester) async {
...@@ -223,6 +228,33 @@ void main() { ...@@ -223,6 +228,33 @@ void main() {
expect(insideTextScaleFactor, 4.0); expect(insideTextScaleFactor, 4.0);
}); });
testWidgets('MediaQuery.platformBrightnessOf', (WidgetTester tester) async {
Brightness outsideBrightness;
Brightness insideBrightness;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
outsideBrightness = MediaQuery.platformBrightnessOf(context);
return MediaQuery(
data: const MediaQueryData(
platformBrightness: Brightness.dark,
),
child: Builder(
builder: (BuildContext context) {
insideBrightness = MediaQuery.platformBrightnessOf(context);
return Container();
},
),
);
},
),
);
expect(outsideBrightness, Brightness.light);
expect(insideBrightness, Brightness.dark);
});
testWidgets('MediaQuery.boldTextOverride', (WidgetTester tester) async { testWidgets('MediaQuery.boldTextOverride', (WidgetTester tester) async {
bool outsideBoldTextOverride; bool outsideBoldTextOverride;
bool insideBoldTextOverride; bool insideBoldTextOverride;
......
...@@ -59,10 +59,12 @@ class TestWindow implements Window { ...@@ -59,10 +59,12 @@ class TestWindow implements Window {
/// Hides the real device pixel ratio and reports the given [devicePixelRatio] instead. /// Hides the real device pixel ratio and reports the given [devicePixelRatio] instead.
set devicePixelRatioTestValue(double devicePixelRatio) { set devicePixelRatioTestValue(double devicePixelRatio) {
_devicePixelRatio = devicePixelRatio; _devicePixelRatio = devicePixelRatio;
onMetricsChanged();
} }
/// Deletes any existing test device pixel ratio and returns to using the real device pixel ratio. /// Deletes any existing test device pixel ratio and returns to using the real device pixel ratio.
void clearDevicePixelRatioTestValue() { void clearDevicePixelRatioTestValue() {
_devicePixelRatio = null; _devicePixelRatio = null;
onMetricsChanged();
} }
@override @override
...@@ -71,10 +73,12 @@ class TestWindow implements Window { ...@@ -71,10 +73,12 @@ class TestWindow implements Window {
/// Hides the real physical size and reports the given [physicalSizeTestValue] instead. /// Hides the real physical size and reports the given [physicalSizeTestValue] instead.
set physicalSizeTestValue (Size physicalSizeTestValue) { set physicalSizeTestValue (Size physicalSizeTestValue) {
_physicalSizeTestValue = physicalSizeTestValue; _physicalSizeTestValue = physicalSizeTestValue;
onMetricsChanged();
} }
/// Deletes any existing test physical size and returns to using the real physical size. /// Deletes any existing test physical size and returns to using the real physical size.
void clearPhysicalSizeTestValue() { void clearPhysicalSizeTestValue() {
_physicalSizeTestValue = null; _physicalSizeTestValue = null;
onMetricsChanged();
} }
@override @override
...@@ -83,10 +87,12 @@ class TestWindow implements Window { ...@@ -83,10 +87,12 @@ class TestWindow implements Window {
/// Hides the real view insets and reports the given [viewInsetsTestValue] instead. /// Hides the real view insets and reports the given [viewInsetsTestValue] instead.
set viewInsetsTestValue(WindowPadding viewInsetsTestValue) { set viewInsetsTestValue(WindowPadding viewInsetsTestValue) {
_viewInsetsTestValue = viewInsetsTestValue; _viewInsetsTestValue = viewInsetsTestValue;
onMetricsChanged();
} }
/// Deletes any existing test view insets and returns to using the real view insets. /// Deletes any existing test view insets and returns to using the real view insets.
void clearViewInsetsTestValue() { void clearViewInsetsTestValue() {
_viewInsetsTestValue = null; _viewInsetsTestValue = null;
onMetricsChanged();
} }
@override @override
...@@ -95,10 +101,12 @@ class TestWindow implements Window { ...@@ -95,10 +101,12 @@ class TestWindow implements Window {
/// Hides the real padding and reports the given [paddingTestValue] instead. /// Hides the real padding and reports the given [paddingTestValue] instead.
set paddingTestValue(WindowPadding paddingTestValue) { set paddingTestValue(WindowPadding paddingTestValue) {
_paddingTestValue = paddingTestValue; _paddingTestValue = paddingTestValue;
onMetricsChanged();
} }
/// Deletes any existing test padding and returns to using the real padding. /// Deletes any existing test padding and returns to using the real padding.
void clearPaddingTestValue() { void clearPaddingTestValue() {
_paddingTestValue = null; _paddingTestValue = null;
onMetricsChanged();
} }
@override @override
...@@ -114,10 +122,12 @@ class TestWindow implements Window { ...@@ -114,10 +122,12 @@ class TestWindow implements Window {
/// Hides the real locale and reports the given [localeTestValue] instead. /// Hides the real locale and reports the given [localeTestValue] instead.
set localeTestValue(Locale localeTestValue) { set localeTestValue(Locale localeTestValue) {
_localeTestValue = localeTestValue; _localeTestValue = localeTestValue;
onLocaleChanged();
} }
/// Deletes any existing test locale and returns to using the real locale. /// Deletes any existing test locale and returns to using the real locale.
void clearLocaleTestValue() { void clearLocaleTestValue() {
_localeTestValue = null; _localeTestValue = null;
onLocaleChanged();
} }
@override @override
...@@ -126,10 +136,12 @@ class TestWindow implements Window { ...@@ -126,10 +136,12 @@ class TestWindow implements Window {
/// Hides the real locales and reports the given [localesTestValue] instead. /// Hides the real locales and reports the given [localesTestValue] instead.
set localesTestValue(List<Locale> localesTestValue) { set localesTestValue(List<Locale> localesTestValue) {
_localesTestValue = localesTestValue; _localesTestValue = localesTestValue;
onLocaleChanged();
} }
/// Deletes any existing test locales and returns to using the real locales. /// Deletes any existing test locales and returns to using the real locales.
void clearLocalesTestValue() { void clearLocalesTestValue() {
_localesTestValue = null; _localesTestValue = null;
onLocaleChanged();
} }
@override @override
...@@ -145,10 +157,12 @@ class TestWindow implements Window { ...@@ -145,10 +157,12 @@ class TestWindow implements Window {
/// Hides the real text scale factor and reports the given [textScaleFactorTestValue] instead. /// Hides the real text scale factor and reports the given [textScaleFactorTestValue] instead.
set textScaleFactorTestValue(double textScaleFactorTestValue) { set textScaleFactorTestValue(double textScaleFactorTestValue) {
_textScaleFactorTestValue = textScaleFactorTestValue; _textScaleFactorTestValue = textScaleFactorTestValue;
onTextScaleFactorChanged();
} }
/// Deletes any existing test text scale factor and returns to using the real text scale factor. /// Deletes any existing test text scale factor and returns to using the real text scale factor.
void clearTextScaleFactorTestValue() { void clearTextScaleFactorTestValue() {
_textScaleFactorTestValue = null; _textScaleFactorTestValue = null;
onTextScaleFactorChanged();
} }
@override @override
...@@ -158,15 +172,17 @@ class TestWindow implements Window { ...@@ -158,15 +172,17 @@ class TestWindow implements Window {
VoidCallback get onPlatformBrightnessChanged => _window.onPlatformBrightnessChanged; VoidCallback get onPlatformBrightnessChanged => _window.onPlatformBrightnessChanged;
@override @override
set onPlatformBrightnessChanged(VoidCallback callback) { set onPlatformBrightnessChanged(VoidCallback callback) {
_window.onPlatformBrightnessChanged =callback; _window.onPlatformBrightnessChanged = callback;
} }
/// Hides the real text scale factor and reports the given [platformBrightnessTestValue] instead. /// Hides the real text scale factor and reports the given [platformBrightnessTestValue] instead.
set platformBrightnessTestValue(Brightness platformBrightnessTestValue) { set platformBrightnessTestValue(Brightness platformBrightnessTestValue) {
_platformBrightnessTestValue = platformBrightnessTestValue; _platformBrightnessTestValue = platformBrightnessTestValue;
onPlatformBrightnessChanged();
} }
/// Deletes any existing test platform brightness and returns to using the real platform brightness. /// Deletes any existing test platform brightness and returns to using the real platform brightness.
void clearPlatformBrightnessTestValue() { void clearPlatformBrightnessTestValue() {
_platformBrightnessTestValue = null; _platformBrightnessTestValue = null;
onPlatformBrightnessChanged();
} }
@override @override
...@@ -237,10 +253,12 @@ class TestWindow implements Window { ...@@ -237,10 +253,12 @@ class TestWindow implements Window {
/// Hides the real semantics enabled and reports the given [semanticsEnabledTestValue] instead. /// Hides the real semantics enabled and reports the given [semanticsEnabledTestValue] instead.
set semanticsEnabledTestValue(bool semanticsEnabledTestValue) { set semanticsEnabledTestValue(bool semanticsEnabledTestValue) {
_semanticsEnabledTestValue = semanticsEnabledTestValue; _semanticsEnabledTestValue = semanticsEnabledTestValue;
onSemanticsEnabledChanged();
} }
/// Deletes any existing test semantics enabled and returns to using the real semantics enabled. /// Deletes any existing test semantics enabled and returns to using the real semantics enabled.
void clearSemanticsEnabledTestValue() { void clearSemanticsEnabledTestValue() {
_semanticsEnabledTestValue = null; _semanticsEnabledTestValue = null;
onSemanticsEnabledChanged();
} }
@override @override
...@@ -263,10 +281,12 @@ class TestWindow implements Window { ...@@ -263,10 +281,12 @@ class TestWindow implements Window {
/// Hides the real accessibility features and reports the given [accessibilityFeaturesTestValue] instead. /// Hides the real accessibility features and reports the given [accessibilityFeaturesTestValue] instead.
set accessibilityFeaturesTestValue(AccessibilityFeatures accessibilityFeaturesTestValue) { set accessibilityFeaturesTestValue(AccessibilityFeatures accessibilityFeaturesTestValue) {
_accessibilityFeaturesTestValue = accessibilityFeaturesTestValue; _accessibilityFeaturesTestValue = accessibilityFeaturesTestValue;
onAccessibilityFeaturesChanged();
} }
/// Deletes any existing test accessibility features and returns to using the real accessibility features. /// Deletes any existing test accessibility features and returns to using the real accessibility features.
void clearAccessibilityFeaturesTestValue() { void clearAccessibilityFeaturesTestValue() {
_accessibilityFeaturesTestValue = null; _accessibilityFeaturesTestValue = null;
onAccessibilityFeaturesChanged();
} }
@override @override
......
...@@ -136,20 +136,6 @@ void main() { ...@@ -136,20 +136,6 @@ void main() {
); );
}); });
testWidgets('TestWindow can fake semantics enabled', (WidgetTester tester) async {
verifyThatTestWindowCanFakeProperty<bool>(
tester: tester,
realValue: ui.window.semanticsEnabled,
fakeValue: !ui.window.semanticsEnabled,
propertyRetriever: () {
return WidgetsBinding.instance.window.semanticsEnabled;
},
propertyFaker: (TestWidgetsFlutterBinding binding, bool fakeValue) {
binding.window.semanticsEnabledTestValue = fakeValue;
}
);
});
testWidgets('TestWindow can fake accessibility features', (WidgetTester tester) async { testWidgets('TestWindow can fake accessibility features', (WidgetTester tester) async {
verifyThatTestWindowCanFakeProperty<AccessibilityFeatures>( verifyThatTestWindowCanFakeProperty<AccessibilityFeatures>(
tester: tester, tester: tester,
......
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