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 ...@@ -2832,7 +2832,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
?? themeData.snackBarTheme.behavior ?? themeData.snackBarTheme.behavior
?? SnackBarBehavior.fixed; ?? SnackBarBehavior.fixed;
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating; isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
snackBarWidth = _messengerSnackBar?._widget.width; snackBarWidth = _messengerSnackBar?._widget.width ?? themeData.snackBarTheme.width;
_addIfNonNull( _addIfNonNull(
children, children,
......
...@@ -293,8 +293,9 @@ class SnackBar extends StatefulWidget { ...@@ -293,8 +293,9 @@ class SnackBar extends StatefulWidget {
/// available space. This property is only used when [behavior] is /// available space. This property is only used when [behavior] is
/// [SnackBarBehavior.floating]. It can not be used if [margin] is specified. /// [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 /// If this property is null, then [SnackBarThemeData.width] of
/// width less the margin. /// [ThemeData.snackBarTheme] is used. If that is null, the snack bar will
/// take up the full device width less the margin.
final double? width; final double? width;
/// The shape of the snack bar's [Material]. /// The shape of the snack bar's [Material].
...@@ -470,6 +471,7 @@ class _SnackBarState extends State<SnackBar> { ...@@ -470,6 +471,7 @@ class _SnackBarState extends State<SnackBar> {
final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium; final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium;
final SnackBarBehavior snackBarBehavior = widget.behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed; final SnackBarBehavior snackBarBehavior = widget.behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed;
final double? width = widget.width ?? snackBarTheme.width;
assert((){ assert((){
// Whether the behavior is set through the constructor or the theme, // Whether the behavior is set through the constructor or the theme,
// assert that our other properties are configured properly. // assert that our other properties are configured properly.
...@@ -485,7 +487,7 @@ class _SnackBarState extends State<SnackBar> { ...@@ -485,7 +487,7 @@ class _SnackBarState extends State<SnackBar> {
} }
} }
assert(widget.margin == null, message('Margin')); assert(widget.margin == null, message('Margin'));
assert(widget.width == null, message('Width')); assert(width == null, message('Width'));
} }
return true; return true;
}()); }());
...@@ -567,10 +569,10 @@ class _SnackBarState extends State<SnackBar> { ...@@ -567,10 +569,10 @@ class _SnackBarState extends State<SnackBar> {
const double topMargin = 5.0; const double topMargin = 5.0;
const double bottomMargin = 10.0; const double bottomMargin = 10.0;
// If width is provided, do not include horizontal margins. // If width is provided, do not include horizontal margins.
if (widget.width != null) { if (width != null) {
snackBar = Container( snackBar = Container(
margin: const EdgeInsets.only(top: topMargin, bottom: bottomMargin), margin: const EdgeInsets.only(top: topMargin, bottom: bottomMargin),
width: widget.width, width: width,
child: snackBar, child: snackBar,
); );
} else { } else {
......
...@@ -60,8 +60,12 @@ class SnackBarThemeData with Diagnosticable { ...@@ -60,8 +60,12 @@ class SnackBarThemeData with Diagnosticable {
this.elevation, this.elevation,
this.shape, this.shape,
this.behavior, 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]. /// Default value for [SnackBar.backgroundColor].
/// ///
/// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`. /// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`.
...@@ -104,6 +108,13 @@ class SnackBarThemeData with Diagnosticable { ...@@ -104,6 +108,13 @@ class SnackBarThemeData with Diagnosticable {
/// If null, [SnackBar] will default to [SnackBarBehavior.fixed]. /// If null, [SnackBar] will default to [SnackBarBehavior.fixed].
final SnackBarBehavior? behavior; 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 /// Creates a copy of this object with the given fields replaced with the
/// new values. /// new values.
SnackBarThemeData copyWith({ SnackBarThemeData copyWith({
...@@ -114,6 +125,7 @@ class SnackBarThemeData with Diagnosticable { ...@@ -114,6 +125,7 @@ class SnackBarThemeData with Diagnosticable {
double? elevation, double? elevation,
ShapeBorder? shape, ShapeBorder? shape,
SnackBarBehavior? behavior, SnackBarBehavior? behavior,
double? width,
}) { }) {
return SnackBarThemeData( return SnackBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor, backgroundColor: backgroundColor ?? this.backgroundColor,
...@@ -123,6 +135,7 @@ class SnackBarThemeData with Diagnosticable { ...@@ -123,6 +135,7 @@ class SnackBarThemeData with Diagnosticable {
elevation: elevation ?? this.elevation, elevation: elevation ?? this.elevation,
shape: shape ?? this.shape, shape: shape ?? this.shape,
behavior: behavior ?? this.behavior, behavior: behavior ?? this.behavior,
width: width ?? this.width,
); );
} }
...@@ -141,19 +154,21 @@ class SnackBarThemeData with Diagnosticable { ...@@ -141,19 +154,21 @@ class SnackBarThemeData with Diagnosticable {
elevation: lerpDouble(a?.elevation, b?.elevation, t), elevation: lerpDouble(a?.elevation, b?.elevation, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t), shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
behavior: t < 0.5 ? a?.behavior : b?.behavior, behavior: t < 0.5 ? a?.behavior : b?.behavior,
width: lerpDouble(a?.width, b?.width, t),
); );
} }
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
backgroundColor, backgroundColor,
actionTextColor, actionTextColor,
disabledActionTextColor, disabledActionTextColor,
contentTextStyle, contentTextStyle,
elevation, elevation,
shape, shape,
behavior, behavior,
); width,
);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
...@@ -170,7 +185,8 @@ class SnackBarThemeData with Diagnosticable { ...@@ -170,7 +185,8 @@ class SnackBarThemeData with Diagnosticable {
&& other.contentTextStyle == contentTextStyle && other.contentTextStyle == contentTextStyle
&& other.elevation == elevation && other.elevation == elevation
&& other.shape == shape && other.shape == shape
&& other.behavior == behavior; && other.behavior == behavior
&& other.width == width;
} }
@override @override
...@@ -183,5 +199,6 @@ class SnackBarThemeData with Diagnosticable { ...@@ -183,5 +199,6 @@ class SnackBarThemeData with Diagnosticable {
properties.add(DoubleProperty('elevation', elevation, defaultValue: null)); properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null)); properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<SnackBarBehavior>('behavior', behavior, defaultValue: null)); properties.add(DiagnosticsProperty<SnackBarBehavior>('behavior', behavior, defaultValue: null));
properties.add(DoubleProperty('width', width, defaultValue: null));
} }
} }
...@@ -724,6 +724,93 @@ void main() { ...@@ -724,6 +724,93 @@ void main() {
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800. 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 { testWidgets('Snackbar labels can be colored', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
......
...@@ -21,9 +21,22 @@ void main() { ...@@ -21,9 +21,22 @@ void main() {
expect(snackBarTheme.elevation, null); expect(snackBarTheme.elevation, null);
expect(snackBarTheme.shape, null); expect(snackBarTheme.shape, null);
expect(snackBarTheme.behavior, 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(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SnackBarThemeData().debugFillProperties(builder); const SnackBarThemeData().debugFillProperties(builder);
...@@ -45,6 +58,7 @@ void main() { ...@@ -45,6 +58,7 @@ void main() {
elevation: 2.0, elevation: 2.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))), shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
width: 400.0,
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
...@@ -60,6 +74,7 @@ void main() { ...@@ -60,6 +74,7 @@ void main() {
'elevation: 2.0', 'elevation: 2.0',
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))', 'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
'behavior: SnackBarBehavior.floating', 'behavior: SnackBarBehavior.floating',
'width: 400.0',
]); ]);
}); });
...@@ -145,6 +160,7 @@ void main() { ...@@ -145,6 +160,7 @@ void main() {
const ShapeBorder shape = RoundedRectangleBorder( const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(9.0)), borderRadius: BorderRadius.all(Radius.circular(9.0)),
); );
const double snackBarWidth = 400.0;
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
theme: ThemeData(snackBarTheme: _snackBarTheme()), theme: ThemeData(snackBarTheme: _snackBarTheme()),
...@@ -155,6 +171,8 @@ void main() { ...@@ -155,6 +171,8 @@ void main() {
onTap: () { onTap: () {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
behavior: SnackBarBehavior.floating,
width: snackBarWidth,
elevation: elevation, elevation: elevation,
shape: shape, shape: shape,
content: const Text('I am a snack bar.'), content: const Text('I am a snack bar.'),
...@@ -177,13 +195,20 @@ void main() { ...@@ -177,13 +195,20 @@ void main() {
await tester.pump(); // start animation await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750)); await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = _getSnackBarMaterialFinder(tester);
final Material material = _getSnackBarMaterial(tester); final Material material = _getSnackBarMaterial(tester);
final RenderParagraph button = _getSnackBarActionTextRenderObject(tester, action); final RenderParagraph button =
_getSnackBarActionTextRenderObject(tester, action);
expect(material.color, backgroundColor); expect(material.color, backgroundColor);
expect(material.elevation, elevation); expect(material.elevation, elevation);
expect(material.shape, shape); expect(material.shape, shape);
expect(button.text.style!.color, textColor); 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 { testWidgets('SnackBar theme behavior is correct for floating', (WidgetTester tester) async {
...@@ -376,10 +401,15 @@ SnackBarThemeData _snackBarTheme() { ...@@ -376,10 +401,15 @@ SnackBarThemeData _snackBarTheme() {
Material _getSnackBarMaterial(WidgetTester tester) { Material _getSnackBarMaterial(WidgetTester tester) {
return tester.widget<Material>( return tester.widget<Material>(
find.descendant( _getSnackBarMaterialFinder(tester).first,
of: find.byType(SnackBar), );
matching: find.byType(Material), }
).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