Made the snackbar fallback theme include a full inverse colorScheme. (#37038)
This commit is contained in:
parent
6ab2445ef5
commit
f0957abcd0
@ -6,6 +6,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'button_theme.dart';
|
import 'button_theme.dart';
|
||||||
|
import 'color_scheme.dart';
|
||||||
import 'flat_button.dart';
|
import 'flat_button.dart';
|
||||||
import 'material.dart';
|
import 'material.dart';
|
||||||
import 'scaffold.dart';
|
import 'scaffold.dart';
|
||||||
@ -14,7 +15,6 @@ import 'theme.dart';
|
|||||||
import 'theme_data.dart';
|
import 'theme_data.dart';
|
||||||
|
|
||||||
const double _singleLineVerticalPadding = 14.0;
|
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
|
// 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
|
// 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<SnackBarAction> {
|
|||||||
/// displayed snack bar, if any, and allows the next to be displayed.
|
/// displayed snack bar, if any, and allows the next to be displayed.
|
||||||
/// * [SnackBarAction], which is used to specify an [action] button to show
|
/// * [SnackBarAction], which is used to specify an [action] button to show
|
||||||
/// on the snack bar.
|
/// on the snack bar.
|
||||||
|
/// * [SnackBarThemeData], to configure the default property values for
|
||||||
|
/// [SnackBar] widgets.
|
||||||
/// * <https://material.io/design/components/snackbars.html>
|
/// * <https://material.io/design/components/snackbars.html>
|
||||||
class SnackBar extends StatelessWidget {
|
class SnackBar extends StatelessWidget {
|
||||||
/// Creates a snack bar.
|
/// Creates a snack bar.
|
||||||
@ -184,7 +186,10 @@ class SnackBar extends StatelessWidget {
|
|||||||
/// Typically a [Text] widget.
|
/// Typically a [Text] widget.
|
||||||
final Widget content;
|
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;
|
final Color backgroundColor;
|
||||||
|
|
||||||
/// The z-coordinate at which to place the snack bar. This controls the size
|
/// 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);
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||||
assert(animation != null);
|
assert(animation != null);
|
||||||
final ThemeData theme = Theme.of(context);
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final ColorScheme colorScheme = theme.colorScheme;
|
||||||
final SnackBarThemeData snackBarTheme = theme.snackBarTheme;
|
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 bool isThemeDark = theme.brightness == Brightness.dark;
|
||||||
final ThemeData darkTheme = ThemeData(
|
|
||||||
brightness: Brightness.dark,
|
// SnackBar uses a theme that is the opposite brightness from
|
||||||
accentColor: theme.accentColor,
|
// the surrounding theme.
|
||||||
accentColorBrightness: theme.accentColorBrightness,
|
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,
|
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 SnackBarBehavior snackBarBehavior = behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed;
|
||||||
final bool isFloatingSnackBar = snackBarBehavior == SnackBarBehavior.floating;
|
final bool isFloatingSnackBar = snackBarBehavior == SnackBarBehavior.floating;
|
||||||
final double snackBarPadding = isFloatingSnackBar ? 16.0 : 24.0;
|
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 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
|
final ShapeBorder shape = this.shape
|
||||||
?? snackBarTheme.shape
|
?? snackBarTheme.shape
|
||||||
?? (isFloatingSnackBar ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)) : null);
|
?? (isFloatingSnackBar ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)) : null);
|
||||||
@ -307,7 +337,7 @@ class SnackBar extends StatelessWidget {
|
|||||||
elevation: elevation,
|
elevation: elevation,
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: darkTheme,
|
data: inverseTheme,
|
||||||
child: mediaQueryData.accessibleNavigation
|
child: mediaQueryData.accessibleNavigation
|
||||||
? snackBar
|
? snackBar
|
||||||
: FadeTransition(
|
: FadeTransition(
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
// 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 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('SnackBar control test', (WidgetTester tester) async {
|
testWidgets('SnackBar control test', (WidgetTester tester) async {
|
||||||
@ -296,6 +297,87 @@ void main() {
|
|||||||
expect(tapCount, equals(1));
|
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 {
|
testWidgets('Snackbar labels can be colored', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -92,7 +92,7 @@ void main() {
|
|||||||
final RenderParagraph content = _getSnackBarTextRenderObject(tester, text);
|
final RenderParagraph content = _getSnackBarTextRenderObject(tester, text);
|
||||||
|
|
||||||
expect(content.text.style, Typography().white.subhead);
|
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.elevation, 6.0);
|
||||||
expect(material.shape, null);
|
expect(material.shape, null);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user