Unverified Commit 29928a46 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Support setting the elevation of disabled floating action buttons (#24728)

Previously, a disabled floating action button always had zero
elevation, which looks dumb.

This also fixes the issue whereby highlightElevation was not honoured
on floating action buttons.

This also fixes an issue I found during testing whereby setState was
being called during build when onHighlightChanged fired due to
onPressed becoming null while a gesture is ongoing (which triggers an
onTapCancel synchronously during build).
parent d7458e3d
034b2a540bc46375cf0c175a0fd512dcd46971e0 7e3f945a906f9f1ffdcb1edba870f4533d2c2b86
...@@ -68,6 +68,10 @@ class RawMaterialButton extends StatefulWidget { ...@@ -68,6 +68,10 @@ class RawMaterialButton extends StatefulWidget {
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged] /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback. /// callback.
///
/// If [onPressed] changes from null to non-null while a gesture is ongoing,
/// this can fire during the build phase (in which case calling
/// [State.setState] is not allowed).
final ValueChanged<bool> onHighlightChanged; final ValueChanged<bool> onHighlightChanged;
/// Defines the default text style, with [Material.textStyle], for the /// Defines the default text style, with [Material.textStyle], for the
...@@ -110,6 +114,8 @@ class RawMaterialButton extends StatefulWidget { ...@@ -110,6 +114,8 @@ class RawMaterialButton extends StatefulWidget {
/// ///
/// Defaults to 0.0. The value is always non-negative. /// Defaults to 0.0. The value is always non-negative.
/// ///
/// See also:
///
/// * [elevation], the default elevation. /// * [elevation], the default elevation.
/// * [highlightElevation], the elevation when the button is pressed. /// * [highlightElevation], the elevation when the button is pressed.
final double disabledElevation; final double disabledElevation;
...@@ -161,11 +167,23 @@ class RawMaterialButton extends StatefulWidget { ...@@ -161,11 +167,23 @@ class RawMaterialButton extends StatefulWidget {
class _RawMaterialButtonState extends State<RawMaterialButton> { class _RawMaterialButtonState extends State<RawMaterialButton> {
bool _highlight = false; bool _highlight = false;
void _handleHighlightChanged(bool value) { void _handleHighlightChanged(bool value) {
setState(() { if (_highlight != value) {
_highlight = value; setState(() {
_highlight = value;
if (widget.onHighlightChanged != null)
widget.onHighlightChanged(value);
});
}
}
@override
void didUpdateWidget(RawMaterialButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (_highlight && !widget.enabled) {
_highlight = false;
if (widget.onHighlightChanged != null) if (widget.onHighlightChanged != null)
widget.onHighlightChanged(value); widget.onHighlightChanged(false);
}); }
} }
@override @override
......
...@@ -444,7 +444,7 @@ class ButtonThemeData extends Diagnosticable { ...@@ -444,7 +444,7 @@ class ButtonThemeData extends Diagnosticable {
} }
/// The [button]'s background color when [MaterialButton.onPressed] is null /// The [button]'s background color when [MaterialButton.onPressed] is null
/// (when MaterialButton.enabled is false). /// (when [MaterialButton.enabled] is false).
/// ///
/// Returns the button's [MaterialButton.disabledColor] if it is non-null. /// Returns the button's [MaterialButton.disabledColor] if it is non-null.
/// ///
......
...@@ -118,7 +118,6 @@ class FlatButton extends MaterialButton { ...@@ -118,7 +118,6 @@ class FlatButton extends MaterialButton {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context); final ButtonThemeData buttonTheme = ButtonTheme.of(context);
return RawMaterialButton( return RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged, onHighlightChanged: onHighlightChanged,
......
...@@ -43,10 +43,15 @@ class _DefaultHeroTag { ...@@ -43,10 +43,15 @@ class _DefaultHeroTag {
/// ///
/// Use at most a single floating action button per screen. Floating action /// Use at most a single floating action button per screen. Floating action
/// buttons should be used for positive actions such as "create", "share", or /// buttons should be used for positive actions such as "create", "share", or
/// "navigate". /// "navigate". (If more than one floating action button is used within a
/// [Route], then make sure that each button has a unique [heroTag], otherwise
/// an exception will be thrown.)
/// ///
/// If the [onPressed] callback is null, then the button will be disabled and /// If the [onPressed] callback is null, then the button will be disabled and
/// will not react to touch. /// will not react to touch. It is highly discouraged to disable a floating
/// action button as there is no indication to the user that the button is
/// disabled. Consider changing the [backgroundColor] if disabling the floating
/// action button.
/// ///
/// See also: /// See also:
/// ///
...@@ -54,12 +59,13 @@ class _DefaultHeroTag { ...@@ -54,12 +59,13 @@ class _DefaultHeroTag {
/// * [RaisedButton], another kind of button that appears to float above the /// * [RaisedButton], another kind of button that appears to float above the
/// content. /// content.
/// * <https://material.io/design/components/buttons-floating-action-button.html> /// * <https://material.io/design/components/buttons-floating-action-button.html>
class FloatingActionButton extends StatefulWidget { class FloatingActionButton extends StatelessWidget {
/// Creates a circular floating action button. /// Creates a circular floating action button.
/// ///
/// The [elevation], [highlightElevation], [mini], [shape], and [clipBehavior] /// The [elevation], [highlightElevation], [mini], [shape], and [clipBehavior]
/// arguments must not be null. Additionally, [elevation] and /// arguments must not be null. Additionally, [elevation],
/// [highlightElevation] must be non-negative. /// [highlightElevation], and [disabledElevation] (if specified) must be
/// non-negative.
const FloatingActionButton({ const FloatingActionButton({
Key key, Key key,
this.child, this.child,
...@@ -69,6 +75,7 @@ class FloatingActionButton extends StatefulWidget { ...@@ -69,6 +75,7 @@ class FloatingActionButton extends StatefulWidget {
this.heroTag = const _DefaultHeroTag(), this.heroTag = const _DefaultHeroTag(),
this.elevation = 6.0, this.elevation = 6.0,
this.highlightElevation = 12.0, this.highlightElevation = 12.0,
double disabledElevation,
@required this.onPressed, @required this.onPressed,
this.mini = false, this.mini = false,
this.shape = const CircleBorder(), this.shape = const CircleBorder(),
...@@ -77,18 +84,21 @@ class FloatingActionButton extends StatefulWidget { ...@@ -77,18 +84,21 @@ class FloatingActionButton extends StatefulWidget {
this.isExtended = false, this.isExtended = false,
}) : assert(elevation != null && elevation >= 0.0), }) : assert(elevation != null && elevation >= 0.0),
assert(highlightElevation != null && highlightElevation >= 0.0), assert(highlightElevation != null && highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(mini != null), assert(mini != null),
assert(shape != null), assert(shape != null),
assert(isExtended != null), assert(isExtended != null),
_sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints, _sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints,
disabledElevation = disabledElevation ?? elevation,
super(key: key); super(key: key);
/// Creates a wider [StadiumBorder] shaped floating action button with both /// Creates a wider [StadiumBorder]-shaped floating action button with both
/// an [icon] and a [label]. /// an [icon] and a [label].
/// ///
/// The [label], [icon], [elevation], [highlightElevation], [clipBehavior] /// The [label], [icon], [elevation], [highlightElevation], [clipBehavior] and
/// and [shape] arguments must not be null. Additionally, [elevation] and /// [shape] arguments must not be null. Additionally, [elevation]
// [highlightElevation] must be non-negative. /// [highlightElevation], and [disabledElevation] (if specified) must be
/// non-negative.
FloatingActionButton.extended({ FloatingActionButton.extended({
Key key, Key key,
this.tooltip, this.tooltip,
...@@ -97,6 +107,7 @@ class FloatingActionButton extends StatefulWidget { ...@@ -97,6 +107,7 @@ class FloatingActionButton extends StatefulWidget {
this.heroTag = const _DefaultHeroTag(), this.heroTag = const _DefaultHeroTag(),
this.elevation = 6.0, this.elevation = 6.0,
this.highlightElevation = 12.0, this.highlightElevation = 12.0,
double disabledElevation,
@required this.onPressed, @required this.onPressed,
this.shape = const StadiumBorder(), this.shape = const StadiumBorder(),
this.isExtended = true, this.isExtended = true,
...@@ -106,10 +117,12 @@ class FloatingActionButton extends StatefulWidget { ...@@ -106,10 +117,12 @@ class FloatingActionButton extends StatefulWidget {
@required Widget label, @required Widget label,
}) : assert(elevation != null && elevation >= 0.0), }) : assert(elevation != null && elevation >= 0.0),
assert(highlightElevation != null && highlightElevation >= 0.0), assert(highlightElevation != null && highlightElevation >= 0.0),
assert(disabledElevation == null || disabledElevation >= 0.0),
assert(shape != null), assert(shape != null),
assert(isExtended != null), assert(isExtended != null),
assert(clipBehavior != null), assert(clipBehavior != null),
_sizeConstraints = _kExtendedSizeConstraints, _sizeConstraints = _kExtendedSizeConstraints,
disabledElevation = disabledElevation ?? elevation,
mini = false, mini = false,
child = _ChildOverflowBox( child = _ChildOverflowBox(
child: Row( child: Row(
...@@ -167,11 +180,15 @@ class FloatingActionButton extends StatefulWidget { ...@@ -167,11 +180,15 @@ class FloatingActionButton extends StatefulWidget {
/// The z-coordinate at which to place this button releative to its parent. /// The z-coordinate at which to place this button releative to its parent.
/// ///
///
/// This controls the size of the shadow below the floating action button. /// This controls the size of the shadow below the floating action button.
/// ///
/// Defaults to 6, the appropriate elevation for floating action buttons. The /// Defaults to 6, the appropriate elevation for floating action buttons. The
/// value is always non-negative. /// value is always non-negative.
///
/// See also:
///
/// * [highlightElevation], the elevation when the button is pressed.
/// * [disabledElevation], the elevation when the button is disabled.
final double elevation; final double elevation;
/// The z-coordinate at which to place this button relative to its parent when /// The z-coordinate at which to place this button relative to its parent when
...@@ -187,6 +204,21 @@ class FloatingActionButton extends StatefulWidget { ...@@ -187,6 +204,21 @@ class FloatingActionButton extends StatefulWidget {
/// * [elevation], the default elevation. /// * [elevation], the default elevation.
final double highlightElevation; final double highlightElevation;
/// The z-coordinate at which to place this button when the button is disabled
/// ([onPressed] is null).
///
/// This controls the size of the shadow below the floating action button.
///
/// Defaults to the same value as [elevation]. Setting this to zero makes the
/// floating action button work similar to a [RaisedButton] but the titular
/// "floating" effect is lost. The value is always non-negative.
///
/// See also:
///
/// * [elevation], the default elevation.
/// * [highlightElevation], the elevation when the button is pressed.
final double disabledElevation;
/// Controls the size of this button. /// Controls the size of this button.
/// ///
/// By default, floating action buttons are non-mini and have a height and /// By default, floating action buttons are non-mini and have a height and
...@@ -229,62 +261,50 @@ class FloatingActionButton extends StatefulWidget { ...@@ -229,62 +261,50 @@ class FloatingActionButton extends StatefulWidget {
final BoxConstraints _sizeConstraints; final BoxConstraints _sizeConstraints;
@override
_FloatingActionButtonState createState() => _FloatingActionButtonState();
}
class _FloatingActionButtonState extends State<FloatingActionButton> {
bool _highlight = false;
void _handleHighlightChanged(bool value) {
setState(() {
_highlight = value;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final Color foregroundColor = widget.foregroundColor ?? theme.accentIconTheme.color; final Color foregroundColor = this.foregroundColor ?? theme.accentIconTheme.color;
Widget result; Widget result;
if (widget.child != null) { if (child != null) {
result = IconTheme.merge( result = IconTheme.merge(
data: IconThemeData( data: IconThemeData(
color: foregroundColor, color: foregroundColor,
), ),
child: widget.child, child: child,
); );
} }
result = RawMaterialButton( result = RawMaterialButton(
onPressed: widget.onPressed, onPressed: onPressed,
onHighlightChanged: _handleHighlightChanged, elevation: elevation,
elevation: _highlight ? widget.highlightElevation : widget.elevation, highlightElevation: highlightElevation,
constraints: widget._sizeConstraints, disabledElevation: disabledElevation,
materialTapTargetSize: widget.materialTapTargetSize ?? theme.materialTapTargetSize, constraints: _sizeConstraints,
fillColor: widget.backgroundColor ?? theme.accentColor, materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
fillColor: backgroundColor ?? theme.accentColor,
textStyle: theme.accentTextTheme.button.copyWith( textStyle: theme.accentTextTheme.button.copyWith(
color: foregroundColor, color: foregroundColor,
letterSpacing: 1.2, letterSpacing: 1.2,
), ),
shape: widget.shape, shape: shape,
clipBehavior: widget.clipBehavior, clipBehavior: clipBehavior,
child: result, child: result,
); );
if (widget.tooltip != null) { if (tooltip != null) {
result = MergeSemantics( result = MergeSemantics(
child: Tooltip( child: Tooltip(
message: widget.tooltip, message: tooltip,
child: result, child: result,
), ),
); );
} }
if (widget.heroTag != null) { if (heroTag != null) {
result = Hero( result = Hero(
tag: widget.heroTag, tag: heroTag,
child: result, child: result,
); );
} }
......
...@@ -242,6 +242,12 @@ class InkResponse extends StatefulWidget { ...@@ -242,6 +242,12 @@ class InkResponse extends StatefulWidget {
/// The value passed to the callback is true if this part of the material has /// The value passed to the callback is true if this part of the material has
/// become highlighted and false if this part of the material has stopped /// become highlighted and false if this part of the material has stopped
/// being highlighted. /// being highlighted.
///
/// If all of [onTap], [onDoubleTap], and [onLongPress] become null while a
/// gesture is ongoing, then [onTapCancel] will be fired and
/// [onHighlightChanged] will be fired with the value false _during the
/// build_. This means, for instance, that in that scenario [State.setState]
/// cannot be called.
final ValueChanged<bool> onHighlightChanged; final ValueChanged<bool> onHighlightChanged;
/// Whether this ink response should be clipped its bounds. /// Whether this ink response should be clipped its bounds.
......
...@@ -73,6 +73,10 @@ class MaterialButton extends StatelessWidget { ...@@ -73,6 +73,10 @@ class MaterialButton extends StatelessWidget {
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged] /// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback. /// callback.
///
/// If [onPressed] changes from null to non-null while a gesture is ongoing,
/// this can fire during the build phase (in which case calling
/// [State.setState] is not allowed).
final ValueChanged<bool> onHighlightChanged; final ValueChanged<bool> onHighlightChanged;
/// Defines the button's base colors, and the defaults for the button's minimum /// Defines the button's base colors, and the defaults for the button's minimum
...@@ -255,6 +259,7 @@ class MaterialButton extends StatelessWidget { ...@@ -255,6 +259,7 @@ class MaterialButton extends StatelessWidget {
return RawMaterialButton( return RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged,
fillColor: color, fillColor: color,
textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)), textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
highlightColor: highlightColor ?? theme.highlightColor, highlightColor: highlightColor ?? theme.highlightColor,
......
...@@ -315,6 +315,27 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide ...@@ -315,6 +315,27 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
); );
} }
@override
void didUpdateWidget(_OutlineButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (_pressed && !widget.enabled) {
_pressed = false;
_controller.reverse();
}
}
void _handleHighlightChanged(bool value) {
if (_pressed == value)
return;
setState(() {
_pressed = value;
if (value)
_controller.forward();
else
_controller.reverse();
});
}
@override @override
void dispose() { void dispose() {
_controller.dispose(); _controller.dispose();
...@@ -375,15 +396,7 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide ...@@ -375,15 +396,7 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
elevation: 0.0, elevation: 0.0,
disabledElevation: 0.0, disabledElevation: 0.0,
highlightElevation: _getHighlightElevation(), highlightElevation: _getHighlightElevation(),
onHighlightChanged: (bool value) { onHighlightChanged: _handleHighlightChanged,
setState(() {
_pressed = value;
if (value)
_controller.forward();
else
_controller.reverse();
});
},
padding: widget.padding, padding: widget.padding,
shape: _OutlineBorder( shape: _OutlineBorder(
shape: widget.shape, shape: widget.shape,
......
...@@ -128,7 +128,6 @@ class RaisedButton extends MaterialButton { ...@@ -128,7 +128,6 @@ class RaisedButton extends MaterialButton {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ButtonThemeData buttonTheme = ButtonTheme.of(context); final ButtonThemeData buttonTheme = ButtonTheme.of(context);
return RawMaterialButton( return RawMaterialButton(
onPressed: onPressed, onPressed: onPressed,
onHighlightChanged: onHighlightChanged, onHighlightChanged: onHighlightChanged,
......
...@@ -110,6 +110,137 @@ void main() { ...@@ -110,6 +110,137 @@ void main() {
expect(find.text('Add'), findsOneWidget); expect(find.text('Add'), findsOneWidget);
}); });
testWidgets('Floating Action Button elevation when highlighted - defaults', (WidgetTester tester) async {
expect(const FloatingActionButton(onPressed: null).highlightElevation, 12.0);
expect(const FloatingActionButton(onPressed: null, highlightElevation: 0.0).highlightElevation, 0.0);
});
testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () { },
),
),
),
);
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
final TestGesture gesture = await tester.press(find.byType(PhysicalShape));
await tester.pump();
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () { },
highlightElevation: 20.0
),
),
),
);
await tester.pump();
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
await gesture.up();
await tester.pump();
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 20.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
});
testWidgets('Floating Action Button elevation when disabled - defaults', (WidgetTester tester) async {
expect(FloatingActionButton(onPressed: () { }).disabledElevation, 6.0);
expect(const FloatingActionButton(onPressed: null).disabledElevation, 6.0);
expect(FloatingActionButton(onPressed: () { }, disabledElevation: 0.0).disabledElevation, 0.0);
});
testWidgets('Floating Action Button elevation when disabled - effect', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
),
),
),
);
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
disabledElevation: 3.0,
),
),
),
);
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 3.0);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () { },
disabledElevation: 3.0,
),
),
),
);
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 3.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
});
testWidgets('Floating Action Button elevation when disabled while highlighted - effect', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () { },
),
),
),
);
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.press(find.byType(PhysicalShape));
await tester.pump();
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: null,
),
),
),
);
await tester.pump();
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 12.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () { },
),
),
),
);
await tester.pump();
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
await tester.pump(const Duration(seconds: 1));
expect(tester.widget<PhysicalShape>(find.byType(PhysicalShape)).elevation, 6.0);
});
testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
final Key key1 = UniqueKey(); final Key key1 = UniqueKey();
await tester.pumpWidget( await tester.pumpWidget(
...@@ -476,7 +607,7 @@ void main() { ...@@ -476,7 +607,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 1000)); await tester.pump(const Duration(milliseconds: 1000));
await expectLater( await expectLater(
find.byKey(key), find.byKey(key),
matchesGoldenFile('floating_action_button_test.clip.1.png'), matchesGoldenFile('floating_action_button_test.clip.2.png'), // .clip.1.png is obsolete and can be removed
skip: !Platform.isLinux, skip: !Platform.isLinux,
); );
}); });
......
...@@ -43,6 +43,25 @@ void main() { ...@@ -43,6 +43,25 @@ void main() {
expect(pressedCount, 1); expect(pressedCount, 1);
}); });
testWidgets('Outline button doesn\'t crash if disabled during a gesture', (WidgetTester tester) async {
Widget buildFrame(VoidCallback onPressed) {
return Directionality(
textDirection: TextDirection.ltr,
child: Theme(
data: ThemeData(),
child: Center(
child: OutlineButton(onPressed: onPressed),
),
),
);
}
await tester.pumpWidget(buildFrame(() { }));
await tester.press(find.byType(OutlineButton));
await tester.pumpAndSettle();
await tester.pumpWidget(buildFrame(null));
await tester.pumpAndSettle();
});
testWidgets('OutlineButton shape and border component overrides', (WidgetTester tester) async { testWidgets('OutlineButton shape and border component overrides', (WidgetTester tester) async {
const Color fillColor = Color(0xFF00FF00); const Color fillColor = Color(0xFF00FF00);
......
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