Add width property to SnackBarThemeData (#112636)
* Adding snackbar theme data width field
* Whitespace formatting
* Update docstrings
* version update
* tidy up
* Revert auto text formatting
* Text formatting
* Remove whitespace
* Test tidy
* Whitespace fix
* y Please enter the commit message for your changes. Lines starting
* whitespace
* test fixes
* de-British-ification
* comment modification
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index e7aae7d..07e69bb 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -2832,7 +2832,7 @@
?? themeData.snackBarTheme.behavior
?? SnackBarBehavior.fixed;
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
- snackBarWidth = _messengerSnackBar?._widget.width;
+ snackBarWidth = _messengerSnackBar?._widget.width ?? themeData.snackBarTheme.width;
_addIfNonNull(
children,
diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart
index d4a2382..e56fc85 100644
--- a/packages/flutter/lib/src/material/snack_bar.dart
+++ b/packages/flutter/lib/src/material/snack_bar.dart
@@ -293,8 +293,9 @@
/// available space. This property is only used when [behavior] is
/// [SnackBarBehavior.floating]. It can not be used if [margin] is specified.
///
- /// If this property is null, then the snack bar will take up the full device
- /// width less the margin.
+ /// If this property is null, then [SnackBarThemeData.width] of
+ /// [ThemeData.snackBarTheme] is used. If that is null, the snack bar will
+ /// take up the full device width less the margin.
final double? width;
/// The shape of the snack bar's [Material].
@@ -470,6 +471,7 @@
final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium;
final SnackBarBehavior snackBarBehavior = widget.behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed;
+ final double? width = widget.width ?? snackBarTheme.width;
assert((){
// Whether the behavior is set through the constructor or the theme,
// assert that our other properties are configured properly.
@@ -485,7 +487,7 @@
}
}
assert(widget.margin == null, message('Margin'));
- assert(widget.width == null, message('Width'));
+ assert(width == null, message('Width'));
}
return true;
}());
@@ -567,10 +569,10 @@
const double topMargin = 5.0;
const double bottomMargin = 10.0;
// If width is provided, do not include horizontal margins.
- if (widget.width != null) {
+ if (width != null) {
snackBar = Container(
margin: const EdgeInsets.only(top: topMargin, bottom: bottomMargin),
- width: widget.width,
+ width: width,
child: snackBar,
);
} else {
diff --git a/packages/flutter/lib/src/material/snack_bar_theme.dart b/packages/flutter/lib/src/material/snack_bar_theme.dart
index 33ab1fa..4e46378 100644
--- a/packages/flutter/lib/src/material/snack_bar_theme.dart
+++ b/packages/flutter/lib/src/material/snack_bar_theme.dart
@@ -60,8 +60,12 @@
this.elevation,
this.shape,
this.behavior,
- }) : assert(elevation == null || elevation >= 0.0);
-
+ this.width,
+ }) : assert(elevation == null || elevation >= 0.0),
+ assert(
+ width == null ||
+ (width != null && identical(behavior, SnackBarBehavior.floating)),
+ 'Width can only be set if behaviour is SnackBarBehavior.floating');
/// Default value for [SnackBar.backgroundColor].
///
/// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`.
@@ -104,6 +108,13 @@
/// If null, [SnackBar] will default to [SnackBarBehavior.fixed].
final SnackBarBehavior? behavior;
+ /// Default value for [SnackBar.width].
+ ///
+ /// If this property is null, then the snack bar will take up the full device
+ /// width less the margin. This value is only used when [behavior] is
+ /// [SnackBarBehavior.floating].
+ final double? width;
+
/// Creates a copy of this object with the given fields replaced with the
/// new values.
SnackBarThemeData copyWith({
@@ -114,6 +125,7 @@
double? elevation,
ShapeBorder? shape,
SnackBarBehavior? behavior,
+ double? width,
}) {
return SnackBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
@@ -123,6 +135,7 @@
elevation: elevation ?? this.elevation,
shape: shape ?? this.shape,
behavior: behavior ?? this.behavior,
+ width: width ?? this.width,
);
}
@@ -141,19 +154,21 @@
elevation: lerpDouble(a?.elevation, b?.elevation, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
behavior: t < 0.5 ? a?.behavior : b?.behavior,
+ width: lerpDouble(a?.width, b?.width, t),
);
}
@override
int get hashCode => Object.hash(
- backgroundColor,
- actionTextColor,
- disabledActionTextColor,
- contentTextStyle,
- elevation,
- shape,
- behavior,
- );
+ backgroundColor,
+ actionTextColor,
+ disabledActionTextColor,
+ contentTextStyle,
+ elevation,
+ shape,
+ behavior,
+ width,
+ );
@override
bool operator ==(Object other) {
@@ -170,7 +185,8 @@
&& other.contentTextStyle == contentTextStyle
&& other.elevation == elevation
&& other.shape == shape
- && other.behavior == behavior;
+ && other.behavior == behavior
+ && other.width == width;
}
@override
@@ -183,5 +199,6 @@
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<SnackBarBehavior>('behavior', behavior, defaultValue: null));
+ properties.add(DoubleProperty('width', width, defaultValue: null));
}
}
diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart
index 46e9db1..53a820a 100644
--- a/packages/flutter/test/material/snack_bar_test.dart
+++ b/packages/flutter/test/material/snack_bar_test.dart
@@ -724,6 +724,93 @@
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
});
+ testWidgets('Snackbar width can be customized from ThemeData',
+ (WidgetTester tester) async {
+ const double width = 200.0;
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData(
+ snackBarTheme: const SnackBarThemeData(
+ width: width, behavior: SnackBarBehavior.floating),
+ ),
+ home: Scaffold(
+ body: Builder(
+ builder: (BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Feeling snackish'),
+ ),
+ );
+ },
+ child: const Text('X'),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+
+ await tester.tap(find.text('X'));
+ await tester.pump(); // start animation
+ await tester.pump(const Duration(milliseconds: 750));
+
+ final Finder materialFinder = find.descendant(
+ of: find.byType(SnackBar),
+ matching: find.byType(Material),
+ );
+ final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
+ final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
+ expect(snackBarBottomLeft.dx, (800 - width) / 2); // Device width is 800.
+ expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
+ });
+
+ testWidgets(
+ 'Snackbar width customization takes preference of widget over theme',
+ (WidgetTester tester) async {
+ const double themeWidth = 200.0;
+ const double widgetWidth = 400.0;
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData(
+ snackBarTheme: const SnackBarThemeData(
+ width: themeWidth, behavior: SnackBarBehavior.floating),
+ ),
+ home: Scaffold(
+ body: Builder(
+ builder: (BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Feeling super snackish'),
+ width: widgetWidth,
+ ),
+ );
+ },
+ child: const Text('X'),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+
+ await tester.tap(find.text('X'));
+ await tester.pump(); // start animation
+ await tester.pump(const Duration(milliseconds: 750));
+
+ final Finder materialFinder = find.descendant(
+ of: find.byType(SnackBar),
+ matching: find.byType(Material),
+ );
+ final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
+ final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
+ expect(snackBarBottomLeft.dx, (800 - widgetWidth) / 2); // Device width is 800.
+ expect(snackBarBottomRight.dx, (800 + widgetWidth) / 2); // Device width is 800.
+ });
+
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 240e27b..7162b5d 100644
--- a/packages/flutter/test/material/snack_bar_theme_test.dart
+++ b/packages/flutter/test/material/snack_bar_theme_test.dart
@@ -21,9 +21,22 @@
expect(snackBarTheme.elevation, null);
expect(snackBarTheme.shape, null);
expect(snackBarTheme.behavior, null);
+ expect(snackBarTheme.width, null);
});
- testWidgets('Default SnackBarThemeData debugFillProperties', (WidgetTester tester) async {
+ test(
+ 'SnackBarTheme throws assertion if width is provided with fixed behaviour',
+ () {
+ expect(
+ () => SnackBarThemeData(
+ behavior: SnackBarBehavior.fixed,
+ width: 300.0,
+ ),
+ throwsAssertionError);
+ });
+
+ testWidgets('Default SnackBarThemeData debugFillProperties',
+ (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SnackBarThemeData().debugFillProperties(builder);
@@ -45,6 +58,7 @@
elevation: 2.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
behavior: SnackBarBehavior.floating,
+ width: 400.0,
).debugFillProperties(builder);
final List<String> description = builder.properties
@@ -60,6 +74,7 @@
'elevation: 2.0',
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
'behavior: SnackBarBehavior.floating',
+ 'width: 400.0',
]);
});
@@ -145,6 +160,7 @@
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(9.0)),
);
+ const double snackBarWidth = 400.0;
await tester.pumpWidget(MaterialApp(
theme: ThemeData(snackBarTheme: _snackBarTheme()),
@@ -155,6 +171,8 @@
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
backgroundColor: backgroundColor,
+ behavior: SnackBarBehavior.floating,
+ width: snackBarWidth,
elevation: elevation,
shape: shape,
content: const Text('I am a snack bar.'),
@@ -177,13 +195,20 @@
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
+ final Finder materialFinder = _getSnackBarMaterialFinder(tester);
final Material material = _getSnackBarMaterial(tester);
- final RenderParagraph button = _getSnackBarActionTextRenderObject(tester, action);
+ final RenderParagraph button =
+ _getSnackBarActionTextRenderObject(tester, action);
expect(material.color, backgroundColor);
expect(material.elevation, elevation);
expect(material.shape, shape);
expect(button.text.style!.color, textColor);
+ // Assert width.
+ final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder.first);
+ final Offset snackBarBottomRight = tester.getBottomRight(materialFinder.first);
+ expect(snackBarBottomLeft.dx, (800 - snackBarWidth) / 2); // Device width is 800.
+ expect(snackBarBottomRight.dx, (800 + snackBarWidth) / 2); // Device width is 800.
});
testWidgets('SnackBar theme behavior is correct for floating', (WidgetTester tester) async {
@@ -376,10 +401,15 @@
Material _getSnackBarMaterial(WidgetTester tester) {
return tester.widget<Material>(
- find.descendant(
- of: find.byType(SnackBar),
- matching: find.byType(Material),
- ).first,
+ _getSnackBarMaterialFinder(tester).first,
+ );
+}
+
+Finder _getSnackBarMaterialFinder(WidgetTester tester) {
+ return find.descendant(
+ of: find.byType(SnackBar),
+ matching: find.byType(Material),
+
);
}