Unverified Commit 29422d25 authored by Eilidh Southren's avatar Eilidh Southren Committed by GitHub

M3 snackbar [re-land] (#116218)

* Add M2 defaults and template skeleton

* add MaterialStateColor functionality to ActionTextColor (issue #110402)

* Add M2 defaults and template skeleton

* updated material 3 tokens

* Updated snackbar demo

* add theme tests

* add gen defaults

* formatting

* more whitespace fixes

* add widget type

* update docs

* code review changes

* Add line overflow functionality

* whitespace fixes

* update M3 animation

* whitespace fixes

* add insetPadding param

* Modifed icon parameter to showCloseIcon

* white space fixes

* test fixes

* rename iconColor to closeIconColor

* debug test fix

* de-britishification

* g3fix

* g3fix

* debug test fix
parent 24b3c384
...@@ -44,6 +44,7 @@ import 'package:gen_defaults/progress_indicator_template.dart'; ...@@ -44,6 +44,7 @@ import 'package:gen_defaults/progress_indicator_template.dart';
import 'package:gen_defaults/radio_template.dart'; import 'package:gen_defaults/radio_template.dart';
import 'package:gen_defaults/segmented_button_template.dart'; import 'package:gen_defaults/segmented_button_template.dart';
import 'package:gen_defaults/slider_template.dart'; import 'package:gen_defaults/slider_template.dart';
import 'package:gen_defaults/snackbar_template.dart';
import 'package:gen_defaults/surface_tint.dart'; import 'package:gen_defaults/surface_tint.dart';
import 'package:gen_defaults/switch_template.dart'; import 'package:gen_defaults/switch_template.dart';
import 'package:gen_defaults/tabs_template.dart'; import 'package:gen_defaults/tabs_template.dart';
...@@ -162,6 +163,7 @@ Future<void> main(List<String> args) async { ...@@ -162,6 +163,7 @@ Future<void> main(List<String> args) async {
ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile(); ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile();
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile(); RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
SegmentedButtonTemplate('SegmentedButton', '$materialLib/segmented_button.dart', tokens).updateFile(); SegmentedButtonTemplate('SegmentedButton', '$materialLib/segmented_button.dart', tokens).updateFile();
SnackbarTemplate('md.comp.snackbar', 'Snackbar', '$materialLib/snack_bar.dart', tokens).updateFile();
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile(); SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile(); SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile(); SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'template.dart';
class SnackbarTemplate extends TokenTemplate {
const SnackbarTemplate(
this.tokenGroup, super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.'
});
final String tokenGroup;
@override
String generate() => '''
class _${blockName}DefaultsM3 extends SnackBarThemeData {
_${blockName}DefaultsM3(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override
Color get backgroundColor => ${componentColor("$tokenGroup.container")};
@override
Color get actionTextColor => MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return ${componentColor("$tokenGroup.action.pressed.label-text")};
}
if (states.contains(MaterialState.pressed)) {
return ${componentColor("$tokenGroup.action.pressed.label-text")};
}
if (states.contains(MaterialState.hovered)) {
return ${componentColor("$tokenGroup.action.hover.label-text")};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor("$tokenGroup.action.focus.label-text")};
}
return ${componentColor("$tokenGroup.action.label-text")};
});
@override
Color get disabledActionTextColor =>
${componentColor("$tokenGroup.action.pressed.label-text")};
@override
TextStyle get contentTextStyle =>
${textStyle("$tokenGroup.supporting-text")}!.copyWith
(color: ${componentColor("$tokenGroup.supporting-text")},
);
@override
double get elevation => ${elevation("$tokenGroup.container")};
@override
ShapeBorder get shape => ${shape("$tokenGroup.container")};
@override
SnackBarBehavior get behavior => SnackBarBehavior.fixed;
@override
EdgeInsets get insetPadding => const EdgeInsets.fromLTRB(15.0, 5.0, 15.0, 10.0);
@override
bool get showCloseIcon => false;
@override
Color get iconColor => _colors.onInverseSurface;
}
''';
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Flutter code sample for [SnackBar] with Material 3 specifications.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
// A Material 3 [SnackBar] demonstrating an optional icon, in either floating
// or fixed format.
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
theme: ThemeData(useMaterial3: true),
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: SnackBarExample(),
),
),
);
}
}
class SnackBarExample extends StatefulWidget {
const SnackBarExample({super.key});
@override
State<SnackBarExample> createState() => _SnackBarExampleState();
}
class _SnackBarExampleState extends State<SnackBarExample> {
SnackBarBehavior? _snackBarBehavior = SnackBarBehavior.floating;
bool _withIcon = true;
bool _withAction = true;
bool _multiLine = false;
bool _longActionLabel = false;
Padding _configRow(List<Widget> children) => Padding(
padding: const EdgeInsets.all(8.0), child: Row(children: children));
@override
Widget build(BuildContext context) {
return Padding(padding: const EdgeInsets.only(left: 50.0), child: Column(
children: <Widget>[
_configRow(<Widget>[
Text('Snack Bar configuration',
style: Theme.of(context).textTheme.bodyLarge),
]),
_configRow(
<Widget>[
const Text('Fixed'),
Radio<SnackBarBehavior>(
value: SnackBarBehavior.fixed,
groupValue: _snackBarBehavior,
onChanged: (SnackBarBehavior? value) {
setState(() {
_snackBarBehavior = value;
});
},
),
const Text('Floating'),
Radio<SnackBarBehavior>(
value: SnackBarBehavior.floating,
groupValue: _snackBarBehavior,
onChanged: (SnackBarBehavior? value) {
setState(() {
_snackBarBehavior = value;
});
},
),
],
),
_configRow(
<Widget>[
const Text('Include Icon '),
Switch(
value: _withIcon,
onChanged: (bool value) {
setState(() {
_withIcon = !_withIcon;
});
},
),
],
),
_configRow(
<Widget>[
const Text('Include Action '),
Switch(
value: _withAction,
onChanged: (bool value) {
setState(() {
_withAction = !_withAction;
});
},
),
const SizedBox(width: 16.0),
const Text('Long Action Label '),
Switch(
value: _longActionLabel,
onChanged: !_withAction
? null
: (bool value) {
setState(() {
_longActionLabel = !_longActionLabel;
});
},
),
],
),
_configRow(
<Widget>[
const Text('Multi Line Text'),
Switch(
value: _multiLine,
onChanged: _snackBarBehavior == SnackBarBehavior.fixed ? null : (bool value) {
setState(() {
_multiLine = !_multiLine;
});
},
),
],
),
const SizedBox(height: 16.0),
ElevatedButton(
child: const Text('Show Snackbar'),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(_snackBar());
}
),
],
),
);
}
SnackBar _snackBar() {
final SnackBarAction? action = _withAction
? SnackBarAction(
label: _longActionLabel ? 'Long Action Text' : 'Action',
onPressed: () {
// Code to execute.
},
)
: null;
final double? width =
_snackBarBehavior == SnackBarBehavior.floating && _multiLine ? 400.0 : null;
final String label = _multiLine
? 'A Snack Bar with quite a lot of text which spans across multiple lines'
: 'Single Line Snack Bar';
return SnackBar(
content: Text(label),
showCloseIcon: _withIcon,
width: width,
behavior: _snackBarBehavior,
action: action,
duration: const Duration(seconds: 3),
);
}
}
...@@ -61,6 +61,9 @@ class SnackBarThemeData with Diagnosticable { ...@@ -61,6 +61,9 @@ class SnackBarThemeData with Diagnosticable {
this.shape, this.shape,
this.behavior, this.behavior,
this.width, this.width,
this.insetPadding,
this.showCloseIcon,
this.closeIconColor,
}) : assert(elevation == null || elevation >= 0.0), }) : assert(elevation == null || elevation >= 0.0),
assert( assert(
width == null || width == null ||
...@@ -115,6 +118,21 @@ class SnackBarThemeData with Diagnosticable { ...@@ -115,6 +118,21 @@ class SnackBarThemeData with Diagnosticable {
/// [SnackBarBehavior.floating]. /// [SnackBarBehavior.floating].
final double? width; final double? width;
/// Overrides the default value for [SnackBar.margin].
///
/// This value is only used when [behavior] is [SnackBarBehavior.floating].
final EdgeInsets? insetPadding;
/// Overrides the default value for [SnackBar.showCloseIcon].
///
/// Whether to show an optional "Close" icon.
final bool? showCloseIcon;
/// Overrides the default value for [SnackBar.closeIconColor].
///
/// This value is only used if [showCloseIcon] is true.
final Color? closeIconColor;
/// 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({
...@@ -126,6 +144,9 @@ class SnackBarThemeData with Diagnosticable { ...@@ -126,6 +144,9 @@ class SnackBarThemeData with Diagnosticable {
ShapeBorder? shape, ShapeBorder? shape,
SnackBarBehavior? behavior, SnackBarBehavior? behavior,
double? width, double? width,
EdgeInsets? insetPadding,
bool? showCloseIcon,
Color? closeIconColor,
}) { }) {
return SnackBarThemeData( return SnackBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor, backgroundColor: backgroundColor ?? this.backgroundColor,
...@@ -136,6 +157,9 @@ class SnackBarThemeData with Diagnosticable { ...@@ -136,6 +157,9 @@ class SnackBarThemeData with Diagnosticable {
shape: shape ?? this.shape, shape: shape ?? this.shape,
behavior: behavior ?? this.behavior, behavior: behavior ?? this.behavior,
width: width ?? this.width, width: width ?? this.width,
insetPadding: insetPadding ?? this.insetPadding,
showCloseIcon: showCloseIcon ?? this.showCloseIcon,
closeIconColor: closeIconColor ?? this.closeIconColor,
); );
} }
...@@ -155,6 +179,8 @@ class SnackBarThemeData with Diagnosticable { ...@@ -155,6 +179,8 @@ class SnackBarThemeData with Diagnosticable {
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), width: lerpDouble(a?.width, b?.width, t),
insetPadding: EdgeInsets.lerp(a?.insetPadding, b?.insetPadding, t),
closeIconColor: Color.lerp(a?.closeIconColor, b?.closeIconColor, t),
); );
} }
...@@ -168,6 +194,9 @@ class SnackBarThemeData with Diagnosticable { ...@@ -168,6 +194,9 @@ class SnackBarThemeData with Diagnosticable {
shape, shape,
behavior, behavior,
width, width,
insetPadding,
showCloseIcon,
closeIconColor,
); );
@override @override
...@@ -186,7 +215,10 @@ class SnackBarThemeData with Diagnosticable { ...@@ -186,7 +215,10 @@ class SnackBarThemeData with Diagnosticable {
&& other.elevation == elevation && other.elevation == elevation
&& other.shape == shape && other.shape == shape
&& other.behavior == behavior && other.behavior == behavior
&& other.width == width; && other.width == width
&& other.insetPadding == insetPadding
&& other.showCloseIcon == showCloseIcon
&& other.closeIconColor == closeIconColor;
} }
@override @override
...@@ -200,5 +232,8 @@ class SnackBarThemeData with Diagnosticable { ...@@ -200,5 +232,8 @@ class SnackBarThemeData with Diagnosticable {
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)); properties.add(DoubleProperty('width', width, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsets>('insetPadding', insetPadding, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showCloseIcon', showCloseIcon, defaultValue: null));
properties.add(ColorProperty('closeIconColor', closeIconColor, defaultValue: null));
} }
} }
...@@ -1339,6 +1339,7 @@ class ThemeData with Diagnosticable { ...@@ -1339,6 +1339,7 @@ class ThemeData with Diagnosticable {
/// * Navigation rail: [NavigationRail] /// * Navigation rail: [NavigationRail]
/// * Progress indicators: [CircularProgressIndicator], [LinearProgressIndicator] /// * Progress indicators: [CircularProgressIndicator], [LinearProgressIndicator]
/// * Radio button: [Radio] /// * Radio button: [Radio]
/// * Snack bar: [SnackBar]
/// * Slider: [Slider] /// * Slider: [Slider]
/// * Switch: [Switch] /// * Switch: [Switch]
/// * Tabs: [TabBar] /// * Tabs: [TabBar]
......
...@@ -316,6 +316,24 @@ void main() { ...@@ -316,6 +316,24 @@ void main() {
' TextButtonTheme\n' ' TextButtonTheme\n'
' Padding\n' ' Padding\n'
' Row\n' ' Row\n'
' Column\n'
' _SingleChildViewport\n'
' IgnorePointer-[GlobalKey#d48e8]\n'
' Semantics\n'
' Listener\n'
' _GestureSemantics\n'
' RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#0c3e0]\n'
' Listener\n'
' _ScrollableScope\n'
' _ScrollSemantics-[GlobalKey#349b8]\n'
' NotificationListener<ScrollMetricsNotification>\n'
' RepaintBoundary\n'
' CustomPaint\n'
' RepaintBoundary\n'
' NotificationListener<ScrollNotification>\n'
' GlowingOverscrollIndicator\n'
' Scrollable\n'
' SingleChildScrollView\n'
' Padding\n' ' Padding\n'
' MediaQuery\n' ' MediaQuery\n'
' Padding\n' ' Padding\n'
......
...@@ -1276,10 +1276,6 @@ void main() { ...@@ -1276,10 +1276,6 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(tester.getSemantics(find.text('snack')), matchesSemantics( expect(tester.getSemantics(find.text('snack')), matchesSemantics(
isLiveRegion: true,
hasDismissAction: true,
hasScrollDownAction: true,
hasScrollUpAction: true,
label: 'snack', label: 'snack',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
)); ));
...@@ -2340,7 +2336,146 @@ void main() { ...@@ -2340,7 +2336,146 @@ void main() {
await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.backdropFilter.png')); await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.backdropFilter.png'));
}); });
testWidgets('ScaffoldMessenger will alert for snackbars that cannot be presented', (WidgetTester tester) async { testWidgets('Floating snackbar can display optional icon', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
bottomSheet: SizedBox(
width: 200,
height: 50,
child: ColoredBox(
color: Colors.pink,
),
),
),
));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('Feeling snackish'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
showCloseIcon: true,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(
'snack_bar.goldenTest.floatingWithActionWithIcon.png'));
});
testWidgets('Fixed width snackbar can display optional icon', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
bottomSheet: SizedBox(
width: 200,
height: 50,
child: ColoredBox(
color: Colors.pink,
),
),
),
));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
scaffoldMessengerState.showSnackBar(SnackBar(
content: const Text('Go get a snack'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
showCloseIcon: true,
behavior: SnackBarBehavior.fixed,
));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.fixedWithActionWithIcon.png'));
});
testWidgets('Fixed snackbar can display optional icon without action', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
bottomSheet: SizedBox(
width: 200,
height: 50,
child: ColoredBox(
color: Colors.pink,
),
),
),
));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text('I wonder if there are snacks nearby?'),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.fixed,
showCloseIcon: true,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.fixedWithIcon.png'));
});
testWidgets(
'Floating width snackbar can display optional icon without action', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
bottomSheet: SizedBox(
width: 200,
height: 50,
child: ColoredBox(
color: Colors.pink,
),
),
),
));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
scaffoldMessengerState.showSnackBar(const SnackBar(
content: Text('Must go get a snack!'),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(find.byType(MaterialApp),
matchesGoldenFile('snack_bar.goldenTest.floatingWithIcon.png'));
});
testWidgets('Fixed multi-line snackbar with icon is aligned correctly', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
bottomSheet: SizedBox(
width: 200,
height: 50,
child: ColoredBox(
color: Colors.pink,
),
),
),
));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
scaffoldMessengerState.showSnackBar(const SnackBar(
content: Text(
'This is a really long snackbar message. So long, it spans across more than one line!'),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(find.byType(MaterialApp),
matchesGoldenFile('snack_bar.goldenTest.multiLineWithIcon.png'));
});
testWidgets(
'ScaffoldMessenger will alert for snackbars that cannot be presented', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/103004 // Regression test for https://github.com/flutter/flutter/issues/103004
await tester.pumpWidget(const MaterialApp( await tester.pumpWidget(const MaterialApp(
home: Center(), home: Center(),
......
...@@ -22,6 +22,9 @@ void main() { ...@@ -22,6 +22,9 @@ void main() {
expect(snackBarTheme.shape, null); expect(snackBarTheme.shape, null);
expect(snackBarTheme.behavior, null); expect(snackBarTheme.behavior, null);
expect(snackBarTheme.width, null); expect(snackBarTheme.width, null);
expect(snackBarTheme.insetPadding, null);
expect(snackBarTheme.showCloseIcon, null);
expect(snackBarTheme.closeIconColor, null);
}); });
test( test(
...@@ -59,6 +62,9 @@ void main() { ...@@ -59,6 +62,9 @@ void main() {
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, width: 400.0,
insetPadding: EdgeInsets.all(10.0),
showCloseIcon: false,
closeIconColor: Color(0xFF0000AA),
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties final List<String> description = builder.properties
...@@ -75,6 +81,9 @@ void main() { ...@@ -75,6 +81,9 @@ void main() {
'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', 'width: 400.0',
'insetPadding: EdgeInsets.all(10.0)',
'showCloseIcon: false',
'closeIconColor: Color(0xff0000aa)',
]); ]);
}); });
...@@ -115,7 +124,7 @@ void main() { ...@@ -115,7 +124,7 @@ void main() {
testWidgets('SnackBar uses values from SnackBarThemeData', (WidgetTester tester) async { testWidgets('SnackBar uses values from SnackBarThemeData', (WidgetTester tester) async {
const String text = 'I am a snack bar.'; const String text = 'I am a snack bar.';
const String action = 'ACTION'; const String action = 'ACTION';
final SnackBarThemeData snackBarTheme = _snackBarTheme(); final SnackBarThemeData snackBarTheme = _snackBarTheme(showCloseIcon: true);
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
theme: ThemeData(snackBarTheme: snackBarTheme), theme: ThemeData(snackBarTheme: snackBarTheme),
...@@ -144,12 +153,14 @@ void main() { ...@@ -144,12 +153,14 @@ void main() {
final Material material = _getSnackBarMaterial(tester); final Material material = _getSnackBarMaterial(tester);
final RenderParagraph button = _getSnackBarActionTextRenderObject(tester, action); final RenderParagraph button = _getSnackBarActionTextRenderObject(tester, action);
final RenderParagraph content = _getSnackBarTextRenderObject(tester, text); final RenderParagraph content = _getSnackBarTextRenderObject(tester, text);
final Icon icon = _getSnackBarIcon(tester);
expect(content.text.style, snackBarTheme.contentTextStyle); expect(content.text.style, snackBarTheme.contentTextStyle);
expect(material.color, snackBarTheme.backgroundColor); expect(material.color, snackBarTheme.backgroundColor);
expect(material.elevation, snackBarTheme.elevation); expect(material.elevation, snackBarTheme.elevation);
expect(material.shape, snackBarTheme.shape); expect(material.shape, snackBarTheme.shape);
expect(button.text.style!.color, snackBarTheme.actionTextColor); expect(button.text.style!.color, snackBarTheme.actionTextColor);
expect(icon.icon, Icons.close);
}); });
testWidgets('SnackBar widget properties take priority over theme', (WidgetTester tester) async { testWidgets('SnackBar widget properties take priority over theme', (WidgetTester tester) async {
...@@ -163,7 +174,7 @@ void main() { ...@@ -163,7 +174,7 @@ void main() {
const double snackBarWidth = 400.0; const double snackBarWidth = 400.0;
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
theme: ThemeData(snackBarTheme: _snackBarTheme()), theme: ThemeData(snackBarTheme: _snackBarTheme(showCloseIcon: true)),
home: Scaffold( home: Scaffold(
body: Builder( body: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
...@@ -182,6 +193,7 @@ void main() { ...@@ -182,6 +193,7 @@ void main() {
label: action, label: action,
onPressed: () {}, onPressed: () {},
), ),
showCloseIcon: false,
)); ));
}, },
child: const Text('X'), child: const Text('X'),
...@@ -204,6 +216,7 @@ void main() { ...@@ -204,6 +216,7 @@ void main() {
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);
expect(_getSnackBarIconFinder(tester), findsNothing);
// Assert width. // Assert width.
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder.first); final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder.first);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder.first); final Offset snackBarBottomRight = tester.getBottomRight(materialFinder.first);
...@@ -214,8 +227,7 @@ void main() { ...@@ -214,8 +227,7 @@ void main() {
testWidgets('SnackBar theme behavior is correct for floating', (WidgetTester tester) async { testWidgets('SnackBar theme behavior is correct for floating', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
theme: ThemeData( theme: ThemeData(
snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating), snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating)),
),
home: Scaffold( home: Scaffold(
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send), child: const Icon(Icons.send),
...@@ -389,13 +401,14 @@ void main() { ...@@ -389,13 +401,14 @@ void main() {
}); });
} }
SnackBarThemeData _snackBarTheme() { SnackBarThemeData _snackBarTheme({bool? showCloseIcon}) {
return const SnackBarThemeData( return SnackBarThemeData(
backgroundColor: Colors.orange, backgroundColor: Colors.orange,
actionTextColor: Colors.green, actionTextColor: Colors.green,
contentTextStyle: TextStyle(color: Colors.blue), contentTextStyle: const TextStyle(color: Colors.blue),
elevation: 12.0, elevation: 12.0,
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), showCloseIcon: showCloseIcon,
shape: const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
); );
} }
...@@ -409,7 +422,6 @@ Finder _getSnackBarMaterialFinder(WidgetTester tester) { ...@@ -409,7 +422,6 @@ Finder _getSnackBarMaterialFinder(WidgetTester tester) {
return find.descendant( return find.descendant(
of: find.byType(SnackBar), of: find.byType(SnackBar),
matching: find.byType(Material), matching: find.byType(Material),
); );
} }
...@@ -420,6 +432,17 @@ RenderParagraph _getSnackBarActionTextRenderObject(WidgetTester tester, String t ...@@ -420,6 +432,17 @@ RenderParagraph _getSnackBarActionTextRenderObject(WidgetTester tester, String t
)); ));
} }
Icon _getSnackBarIcon(WidgetTester tester) {
return tester.widget<Icon>(_getSnackBarIconFinder(tester));
}
Finder _getSnackBarIconFinder(WidgetTester tester) {
return find.descendant(
of: find.byType(SnackBar),
matching: find.byIcon(Icons.close),
);
}
RenderParagraph _getSnackBarTextRenderObject(WidgetTester tester, String text) { RenderParagraph _getSnackBarTextRenderObject(WidgetTester tester, String text) {
return tester.renderObject(find.descendant( return tester.renderObject(find.descendant(
of: find.byType(SnackBar), of: find.byType(SnackBar),
......
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