Skip to content

Commit 4862a84

Browse files
authored
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
1 parent 2481108 commit 4862a84

File tree

5 files changed

+159
-23
lines changed

5 files changed

+159
-23
lines changed

packages/flutter/lib/src/material/scaffold.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2832,7 +2832,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
28322832
?? themeData.snackBarTheme.behavior
28332833
?? SnackBarBehavior.fixed;
28342834
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
2835-
snackBarWidth = _messengerSnackBar?._widget.width;
2835+
snackBarWidth = _messengerSnackBar?._widget.width ?? themeData.snackBarTheme.width;
28362836

28372837
_addIfNonNull(
28382838
children,

packages/flutter/lib/src/material/snack_bar.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,9 @@ class SnackBar extends StatefulWidget {
293293
/// available space. This property is only used when [behavior] is
294294
/// [SnackBarBehavior.floating]. It can not be used if [margin] is specified.
295295
///
296-
/// If this property is null, then the snack bar will take up the full device
297-
/// width less the margin.
296+
/// If this property is null, then [SnackBarThemeData.width] of
297+
/// [ThemeData.snackBarTheme] is used. If that is null, the snack bar will
298+
/// take up the full device width less the margin.
298299
final double? width;
299300

300301
/// The shape of the snack bar's [Material].
@@ -470,6 +471,7 @@ class _SnackBarState extends State<SnackBar> {
470471

471472
final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium;
472473
final SnackBarBehavior snackBarBehavior = widget.behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed;
474+
final double? width = widget.width ?? snackBarTheme.width;
473475
assert((){
474476
// Whether the behavior is set through the constructor or the theme,
475477
// assert that our other properties are configured properly.
@@ -485,7 +487,7 @@ class _SnackBarState extends State<SnackBar> {
485487
}
486488
}
487489
assert(widget.margin == null, message('Margin'));
488-
assert(widget.width == null, message('Width'));
490+
assert(width == null, message('Width'));
489491
}
490492
return true;
491493
}());
@@ -567,10 +569,10 @@ class _SnackBarState extends State<SnackBar> {
567569
const double topMargin = 5.0;
568570
const double bottomMargin = 10.0;
569571
// If width is provided, do not include horizontal margins.
570-
if (widget.width != null) {
572+
if (width != null) {
571573
snackBar = Container(
572574
margin: const EdgeInsets.only(top: topMargin, bottom: bottomMargin),
573-
width: widget.width,
575+
width: width,
574576
child: snackBar,
575577
);
576578
} else {

packages/flutter/lib/src/material/snack_bar_theme.dart

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,12 @@ class SnackBarThemeData with Diagnosticable {
6060
this.elevation,
6161
this.shape,
6262
this.behavior,
63-
}) : assert(elevation == null || elevation >= 0.0);
64-
63+
this.width,
64+
}) : assert(elevation == null || elevation >= 0.0),
65+
assert(
66+
width == null ||
67+
(width != null && identical(behavior, SnackBarBehavior.floating)),
68+
'Width can only be set if behaviour is SnackBarBehavior.floating');
6569
/// Default value for [SnackBar.backgroundColor].
6670
///
6771
/// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`.
@@ -104,6 +108,13 @@ class SnackBarThemeData with Diagnosticable {
104108
/// If null, [SnackBar] will default to [SnackBarBehavior.fixed].
105109
final SnackBarBehavior? behavior;
106110

111+
/// Default value for [SnackBar.width].
112+
///
113+
/// If this property is null, then the snack bar will take up the full device
114+
/// width less the margin. This value is only used when [behavior] is
115+
/// [SnackBarBehavior.floating].
116+
final double? width;
117+
107118
/// Creates a copy of this object with the given fields replaced with the
108119
/// new values.
109120
SnackBarThemeData copyWith({
@@ -114,6 +125,7 @@ class SnackBarThemeData with Diagnosticable {
114125
double? elevation,
115126
ShapeBorder? shape,
116127
SnackBarBehavior? behavior,
128+
double? width,
117129
}) {
118130
return SnackBarThemeData(
119131
backgroundColor: backgroundColor ?? this.backgroundColor,
@@ -123,6 +135,7 @@ class SnackBarThemeData with Diagnosticable {
123135
elevation: elevation ?? this.elevation,
124136
shape: shape ?? this.shape,
125137
behavior: behavior ?? this.behavior,
138+
width: width ?? this.width,
126139
);
127140
}
128141

@@ -141,19 +154,21 @@ class SnackBarThemeData with Diagnosticable {
141154
elevation: lerpDouble(a?.elevation, b?.elevation, t),
142155
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
143156
behavior: t < 0.5 ? a?.behavior : b?.behavior,
157+
width: lerpDouble(a?.width, b?.width, t),
144158
);
145159
}
146160

147161
@override
148162
int get hashCode => Object.hash(
149-
backgroundColor,
150-
actionTextColor,
151-
disabledActionTextColor,
152-
contentTextStyle,
153-
elevation,
154-
shape,
155-
behavior,
156-
);
163+
backgroundColor,
164+
actionTextColor,
165+
disabledActionTextColor,
166+
contentTextStyle,
167+
elevation,
168+
shape,
169+
behavior,
170+
width,
171+
);
157172

158173
@override
159174
bool operator ==(Object other) {
@@ -170,7 +185,8 @@ class SnackBarThemeData with Diagnosticable {
170185
&& other.contentTextStyle == contentTextStyle
171186
&& other.elevation == elevation
172187
&& other.shape == shape
173-
&& other.behavior == behavior;
188+
&& other.behavior == behavior
189+
&& other.width == width;
174190
}
175191

176192
@override
@@ -183,5 +199,6 @@ class SnackBarThemeData with Diagnosticable {
183199
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
184200
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
185201
properties.add(DiagnosticsProperty<SnackBarBehavior>('behavior', behavior, defaultValue: null));
202+
properties.add(DoubleProperty('width', width, defaultValue: null));
186203
}
187204
}

packages/flutter/test/material/snack_bar_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,93 @@ void main() {
724724
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
725725
});
726726

727+
testWidgets('Snackbar width can be customized from ThemeData',
728+
(WidgetTester tester) async {
729+
const double width = 200.0;
730+
await tester.pumpWidget(
731+
MaterialApp(
732+
theme: ThemeData(
733+
snackBarTheme: const SnackBarThemeData(
734+
width: width, behavior: SnackBarBehavior.floating),
735+
),
736+
home: Scaffold(
737+
body: Builder(
738+
builder: (BuildContext context) {
739+
return GestureDetector(
740+
onTap: () {
741+
ScaffoldMessenger.of(context).showSnackBar(
742+
const SnackBar(
743+
content: Text('Feeling snackish'),
744+
),
745+
);
746+
},
747+
child: const Text('X'),
748+
);
749+
},
750+
),
751+
),
752+
),
753+
);
754+
755+
await tester.tap(find.text('X'));
756+
await tester.pump(); // start animation
757+
await tester.pump(const Duration(milliseconds: 750));
758+
759+
final Finder materialFinder = find.descendant(
760+
of: find.byType(SnackBar),
761+
matching: find.byType(Material),
762+
);
763+
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
764+
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
765+
expect(snackBarBottomLeft.dx, (800 - width) / 2); // Device width is 800.
766+
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
767+
});
768+
769+
testWidgets(
770+
'Snackbar width customization takes preference of widget over theme',
771+
(WidgetTester tester) async {
772+
const double themeWidth = 200.0;
773+
const double widgetWidth = 400.0;
774+
await tester.pumpWidget(
775+
MaterialApp(
776+
theme: ThemeData(
777+
snackBarTheme: const SnackBarThemeData(
778+
width: themeWidth, behavior: SnackBarBehavior.floating),
779+
),
780+
home: Scaffold(
781+
body: Builder(
782+
builder: (BuildContext context) {
783+
return GestureDetector(
784+
onTap: () {
785+
ScaffoldMessenger.of(context).showSnackBar(
786+
const SnackBar(
787+
content: Text('Feeling super snackish'),
788+
width: widgetWidth,
789+
),
790+
);
791+
},
792+
child: const Text('X'),
793+
);
794+
},
795+
),
796+
),
797+
),
798+
);
799+
800+
await tester.tap(find.text('X'));
801+
await tester.pump(); // start animation
802+
await tester.pump(const Duration(milliseconds: 750));
803+
804+
final Finder materialFinder = find.descendant(
805+
of: find.byType(SnackBar),
806+
matching: find.byType(Material),
807+
);
808+
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
809+
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
810+
expect(snackBarBottomLeft.dx, (800 - widgetWidth) / 2); // Device width is 800.
811+
expect(snackBarBottomRight.dx, (800 + widgetWidth) / 2); // Device width is 800.
812+
});
813+
727814
testWidgets('Snackbar labels can be colored', (WidgetTester tester) async {
728815
await tester.pumpWidget(
729816
MaterialApp(

packages/flutter/test/material/snack_bar_theme_test.dart

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,22 @@ void main() {
2121
expect(snackBarTheme.elevation, null);
2222
expect(snackBarTheme.shape, null);
2323
expect(snackBarTheme.behavior, null);
24+
expect(snackBarTheme.width, null);
2425
});
2526

26-
testWidgets('Default SnackBarThemeData debugFillProperties', (WidgetTester tester) async {
27+
test(
28+
'SnackBarTheme throws assertion if width is provided with fixed behaviour',
29+
() {
30+
expect(
31+
() => SnackBarThemeData(
32+
behavior: SnackBarBehavior.fixed,
33+
width: 300.0,
34+
),
35+
throwsAssertionError);
36+
});
37+
38+
testWidgets('Default SnackBarThemeData debugFillProperties',
39+
(WidgetTester tester) async {
2740
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
2841
const SnackBarThemeData().debugFillProperties(builder);
2942

@@ -45,6 +58,7 @@ void main() {
4558
elevation: 2.0,
4659
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
4760
behavior: SnackBarBehavior.floating,
61+
width: 400.0,
4862
).debugFillProperties(builder);
4963

5064
final List<String> description = builder.properties
@@ -60,6 +74,7 @@ void main() {
6074
'elevation: 2.0',
6175
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
6276
'behavior: SnackBarBehavior.floating',
77+
'width: 400.0',
6378
]);
6479
});
6580

@@ -145,6 +160,7 @@ void main() {
145160
const ShapeBorder shape = RoundedRectangleBorder(
146161
borderRadius: BorderRadius.all(Radius.circular(9.0)),
147162
);
163+
const double snackBarWidth = 400.0;
148164

149165
await tester.pumpWidget(MaterialApp(
150166
theme: ThemeData(snackBarTheme: _snackBarTheme()),
@@ -155,6 +171,8 @@ void main() {
155171
onTap: () {
156172
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
157173
backgroundColor: backgroundColor,
174+
behavior: SnackBarBehavior.floating,
175+
width: snackBarWidth,
158176
elevation: elevation,
159177
shape: shape,
160178
content: const Text('I am a snack bar.'),
@@ -177,13 +195,20 @@ void main() {
177195
await tester.pump(); // start animation
178196
await tester.pump(const Duration(milliseconds: 750));
179197

198+
final Finder materialFinder = _getSnackBarMaterialFinder(tester);
180199
final Material material = _getSnackBarMaterial(tester);
181-
final RenderParagraph button = _getSnackBarActionTextRenderObject(tester, action);
200+
final RenderParagraph button =
201+
_getSnackBarActionTextRenderObject(tester, action);
182202

183203
expect(material.color, backgroundColor);
184204
expect(material.elevation, elevation);
185205
expect(material.shape, shape);
186206
expect(button.text.style!.color, textColor);
207+
// Assert width.
208+
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder.first);
209+
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder.first);
210+
expect(snackBarBottomLeft.dx, (800 - snackBarWidth) / 2); // Device width is 800.
211+
expect(snackBarBottomRight.dx, (800 + snackBarWidth) / 2); // Device width is 800.
187212
});
188213

189214
testWidgets('SnackBar theme behavior is correct for floating', (WidgetTester tester) async {
@@ -376,10 +401,15 @@ SnackBarThemeData _snackBarTheme() {
376401

377402
Material _getSnackBarMaterial(WidgetTester tester) {
378403
return tester.widget<Material>(
379-
find.descendant(
380-
of: find.byType(SnackBar),
381-
matching: find.byType(Material),
382-
).first,
404+
_getSnackBarMaterialFinder(tester).first,
405+
);
406+
}
407+
408+
Finder _getSnackBarMaterialFinder(WidgetTester tester) {
409+
return find.descendant(
410+
of: find.byType(SnackBar),
411+
matching: find.byType(Material),
412+
383413
);
384414
}
385415

0 commit comments

Comments
 (0)