diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index ae0317e7f0..e5e63986ee 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -6,6 +6,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'button_theme.dart'; +import 'color_scheme.dart'; import 'flat_button.dart'; import 'material.dart'; import 'scaffold.dart'; @@ -14,7 +15,6 @@ import 'theme.dart'; import 'theme_data.dart'; const double _singleLineVerticalPadding = 14.0; -const Color _snackBarBackgroundColor = Color(0xFF323232); // TODO(ianh): We should check if the given text and actions are going to fit on // one line or not, and if they are, use the single-line layout, and if not, use @@ -158,6 +158,8 @@ class _SnackBarActionState extends State { /// displayed snack bar, if any, and allows the next to be displayed. /// * [SnackBarAction], which is used to specify an [action] button to show /// on the snack bar. +/// * [SnackBarThemeData], to configure the default property values for +/// [SnackBar] widgets. /// * class SnackBar extends StatelessWidget { /// Creates a snack bar. @@ -184,7 +186,10 @@ class SnackBar extends StatelessWidget { /// Typically a [Text] widget. final Widget content; - /// The Snackbar's background color. By default the color is dark grey. + /// The Snackbar's background color. If not specified it will use + /// [ThemeData.snackBarTheme.backgroundColor]. If that is not specified + /// it will default to a dark variation of [ColorScheme.surface] for light + /// themes, or [ColorScheme.onSurface] for dark themes. final Color backgroundColor; /// The z-coordinate at which to place the snack bar. This controls the size @@ -245,15 +250,40 @@ class SnackBar extends StatelessWidget { final MediaQueryData mediaQueryData = MediaQuery.of(context); assert(animation != null); final ThemeData theme = Theme.of(context); + final ColorScheme colorScheme = theme.colorScheme; final SnackBarThemeData snackBarTheme = theme.snackBarTheme; - // TODO(rami-a): Use a light theme if the app has a dark theme, https://github.com/flutter/flutter/issues/31418 - final ThemeData darkTheme = ThemeData( - brightness: Brightness.dark, - accentColor: theme.accentColor, - accentColorBrightness: theme.accentColorBrightness, + final bool isThemeDark = theme.brightness == Brightness.dark; + + // SnackBar uses a theme that is the opposite brightness from + // the surrounding theme. + final Brightness brightness = isThemeDark ? Brightness.light : Brightness.dark; + final Color themeBackgroundColor = isThemeDark + ? colorScheme.onSurface + : Color.alphaBlend(colorScheme.onSurface.withOpacity(0.80), colorScheme.surface); + final ThemeData inverseTheme = ThemeData( + brightness: brightness, + backgroundColor: themeBackgroundColor, + colorScheme: ColorScheme( + primary: colorScheme.onPrimary, + primaryVariant: colorScheme.onPrimary, + // For the button color, the spec says it should be primaryVariant, but for + // backward compatibility on light themes we are leaving it as secondary. + secondary: isThemeDark ? colorScheme.primaryVariant : colorScheme.secondary, + secondaryVariant: colorScheme.onSecondary, + surface: colorScheme.onSurface, + background: themeBackgroundColor, + error: colorScheme.onError, + onPrimary: colorScheme.primary, + onSecondary: colorScheme.secondary, + onSurface: colorScheme.surface, + onBackground: colorScheme.background, + onError: colorScheme.error, + brightness: brightness, + ), snackBarTheme: snackBarTheme, ); - final TextStyle contentTextStyle = snackBarTheme.contentTextStyle ?? darkTheme.textTheme.subhead; + + final TextStyle contentTextStyle = snackBarTheme.contentTextStyle ?? inverseTheme.textTheme.subhead; final SnackBarBehavior snackBarBehavior = behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed; final bool isFloatingSnackBar = snackBarBehavior == SnackBarBehavior.floating; final double snackBarPadding = isFloatingSnackBar ? 16.0 : 24.0; @@ -297,7 +327,7 @@ class SnackBar extends StatelessWidget { ); final double elevation = this.elevation ?? snackBarTheme.elevation ?? 6.0; - final Color backgroundColor = this.backgroundColor ?? snackBarTheme.backgroundColor ?? _snackBarBackgroundColor; + final Color backgroundColor = this.backgroundColor ?? snackBarTheme.backgroundColor ?? inverseTheme.backgroundColor; final ShapeBorder shape = this.shape ?? snackBarTheme.shape ?? (isFloatingSnackBar ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)) : null); @@ -307,7 +337,7 @@ class SnackBar extends StatelessWidget { elevation: elevation, color: backgroundColor, child: Theme( - data: darkTheme, + data: inverseTheme, child: mediaQueryData.accessibleNavigation ? snackBar : FadeTransition( diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart index 068d0a74ca..f49dccc427 100644 --- a/packages/flutter/test/material/snack_bar_test.dart +++ b/packages/flutter/test/material/snack_bar_test.dart @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('SnackBar control test', (WidgetTester tester) async { @@ -296,6 +297,87 @@ void main() { expect(tapCount, equals(1)); }); + testWidgets('Light theme SnackBar has dark background', (WidgetTester tester) async { + final ThemeData lightTheme = ThemeData.light(); + await tester.pumpWidget( + MaterialApp( + theme: lightTheme, + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + Scaffold.of(context).showSnackBar( + SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction( + label: 'ACTION', + onPressed: () { }, + ), + ), + ); + }, + child: const Text('X'), + ); + } + ), + ), + ), + ); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); + + final RenderPhysicalModel renderModel = tester.renderObject( + find.widgetWithText(Material, 'I am a snack bar.').first + ); + // There is a somewhat complicated background color calculation based + // off of the surface color. For the default light theme it + // should be this value. + expect(renderModel.color, equals(const Color(0xFF333333))); + }); + + testWidgets('Dark theme SnackBar has light background', (WidgetTester tester) async { + final ThemeData darkTheme = ThemeData.dark(); + await tester.pumpWidget( + MaterialApp( + theme: darkTheme, + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + Scaffold.of(context).showSnackBar( + SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction( + label: 'ACTION', + onPressed: () { }, + ), + ), + ); + }, + child: const Text('X'), + ); + } + ), + ), + ), + ); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); + + final RenderPhysicalModel renderModel = tester.renderObject( + find.widgetWithText(Material, 'I am a snack bar.').first + ); + expect(renderModel.color, equals(darkTheme.colorScheme.onSurface)); + }); + testWidgets('Snackbar labels can be colored', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( diff --git a/packages/flutter/test/material/snack_bar_theme_test.dart b/packages/flutter/test/material/snack_bar_theme_test.dart index 9e418a24a5..4f6f37fc25 100644 --- a/packages/flutter/test/material/snack_bar_theme_test.dart +++ b/packages/flutter/test/material/snack_bar_theme_test.dart @@ -92,7 +92,7 @@ void main() { final RenderParagraph content = _getSnackBarTextRenderObject(tester, text); expect(content.text.style, Typography().white.subhead); - expect(material.color, const Color(0xFF323232)); + expect(material.color, const Color(0xFF333333)); expect(material.elevation, 6.0); expect(material.shape, null); });