Unverified Commit 4862a84b authored by Eilidh Southren's avatar Eilidh Southren Committed by GitHub

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
parent 24811083
......@@ -2832,7 +2832,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
?? themeData.snackBarTheme.behavior
?? SnackBarBehavior.fixed;
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
snackBarWidth = _messengerSnackBar?._widget.width;
snackBarWidth = _messengerSnackBar?._widget.width ?? themeData.snackBarTheme.width;
_addIfNonNull(
children,
......
......@@ -293,8 +293,9 @@ class SnackBar extends StatefulWidget {
/// 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 @@ class _SnackBarState extends State<SnackBar> {
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 @@ class _SnackBarState extends State<SnackBar> {
}
}
assert(widget.margin == null, message('Margin'));
assert(widget.width == null, message('Width'));
assert(width == null, message('Width'));
}
return true;
}());
......@@ -567,10 +569,10 @@ class _SnackBarState extends State<SnackBar> {
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 {
......
......@@ -60,8 +60,12 @@ class SnackBarThemeData with Diagnosticable {
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 @@ class SnackBarThemeData with Diagnosticable {
/// 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 @@ class SnackBarThemeData with Diagnosticable {
double? elevation,
ShapeBorder? shape,
SnackBarBehavior? behavior,
double? width,
}) {
return SnackBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
......@@ -123,6 +135,7 @@ class SnackBarThemeData with Diagnosticable {
elevation: elevation ?? this.elevation,
shape: shape ?? this.shape,
behavior: behavior ?? this.behavior,
width: width ?? this.width,
);
}
......@@ -141,19 +154,21 @@ class SnackBarThemeData with Diagnosticable {
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 @@ class SnackBarThemeData with Diagnosticable {
&& other.contentTextStyle == contentTextStyle
&& other.elevation == elevation
&& other.shape == shape
&& other.behavior == behavior;
&& other.behavior == behavior
&& other.width == width;
}
@override
......@@ -183,5 +199,6 @@ class SnackBarThemeData with Diagnosticable {
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));
}
}
......@@ -724,6 +724,93 @@ void main() {
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(
......
......@@ -21,9 +21,22 @@ void main() {
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 @@ void main() {
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 @@ void main() {
'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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ SnackBarThemeData _snackBarTheme() {
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),
);
}
......
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