Unverified Commit 9cf042ec authored by Hans Muller's avatar Hans Muller Committed by GitHub

Revert "Added MaterialStatesController, updated InkWell et al. (#103167)" (#105138)

This reverts commit 180566f2.
parent 62021a9c
// 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 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: Home()));
}
class SelectableButton extends StatefulWidget {
const SelectableButton({
super.key,
required this.selected,
this.style,
required this.onPressed,
required this.child,
});
final bool selected;
final ButtonStyle? style;
final VoidCallback? onPressed;
final Widget child;
@override
State<SelectableButton> createState() => _SelectableButtonState();
}
class _SelectableButtonState extends State<SelectableButton> {
late final MaterialStatesController statesController;
@override
void initState() {
super.initState();
statesController = MaterialStatesController(<MaterialState>{
if (widget.selected) MaterialState.selected
});
}
@override
void didUpdateWidget(SelectableButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selected != oldWidget.selected) {
statesController.update(MaterialState.selected, widget.selected);
}
}
@override
Widget build(BuildContext context) {
return TextButton(
statesController: statesController,
style: widget.style,
onPressed: widget.onPressed,
child: widget.child,
);
}
}
class Home extends StatefulWidget {
const Home({ super.key });
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool selected = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SelectableButton(
selected: selected,
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return Colors.white;
}
return null; // defer to the defaults
},
),
backgroundColor: MaterialStateProperty.resolveWith<Color?>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return Colors.indigo;
}
return null; // defer to the defaults
},
),
),
onPressed: () {
setState(() { selected = !selected; });
},
child: const Text('toggle selected'),
),
),
);
}
}
// 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 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/text_button/text_button.1.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('SelectableButton', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.light(),
),
home: const example.Home(),
),
);
final Finder button = find.byType(example.SelectableButton);
example.SelectableButton buttonWidget() => tester.widget<example.SelectableButton>(button);
Material buttonMaterial() {
return tester.widget<Material>(
find.descendant(
of: find.byType(example.SelectableButton),
matching: find.byType(Material),
),
);
}
expect(buttonWidget().selected, false);
expect(buttonMaterial().textStyle!.color, const ColorScheme.light().primary); // default button foreground color
expect(buttonMaterial().color, Colors.transparent); // default button background color
await tester.tap(button); // Toggles the button's selected property.
await tester.pumpAndSettle();
expect(buttonWidget().selected, true);
expect(buttonMaterial().textStyle!.color, Colors.white);
expect(buttonMaterial().color, Colors.indigo);
await tester.tap(button); // Toggles the button's selected property.
await tester.pumpAndSettle();
expect(buttonWidget().selected, false);
expect(buttonMaterial().textStyle!.color, const ColorScheme.light().primary);
expect(buttonMaterial().color, Colors.transparent);
});
}
...@@ -14,6 +14,7 @@ import 'constants.dart'; ...@@ -14,6 +14,7 @@ import 'constants.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'material.dart'; import 'material.dart';
import 'material_state.dart'; import 'material_state.dart';
import 'material_state_mixin.dart';
import 'theme_data.dart'; import 'theme_data.dart';
/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object. /// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
...@@ -38,7 +39,6 @@ abstract class ButtonStyleButton extends StatefulWidget { ...@@ -38,7 +39,6 @@ abstract class ButtonStyleButton extends StatefulWidget {
required this.focusNode, required this.focusNode,
required this.autofocus, required this.autofocus,
required this.clipBehavior, required this.clipBehavior,
this.statesController,
required this.child, required this.child,
}) : assert(autofocus != null), }) : assert(autofocus != null),
assert(clipBehavior != null); assert(clipBehavior != null);
...@@ -95,9 +95,6 @@ abstract class ButtonStyleButton extends StatefulWidget { ...@@ -95,9 +95,6 @@ abstract class ButtonStyleButton extends StatefulWidget {
/// {@macro flutter.widgets.Focus.autofocus} /// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus; final bool autofocus;
/// {@macro flutter.material.inkwell.statesController}
final MaterialStatesController? statesController;
/// Typically the button's label. /// Typically the button's label.
final Widget? child; final Widget? child;
...@@ -194,59 +191,34 @@ abstract class ButtonStyleButton extends StatefulWidget { ...@@ -194,59 +191,34 @@ abstract class ButtonStyleButton extends StatefulWidget {
/// * [TextButton], a simple button without a shadow. /// * [TextButton], a simple button without a shadow.
/// * [ElevatedButton], a filled button whose material elevates when pressed. /// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [OutlinedButton], similar to [TextButton], but with an outline. /// * [OutlinedButton], similar to [TextButton], but with an outline.
class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStateMixin { class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin, TickerProviderStateMixin {
AnimationController? controller; AnimationController? _controller;
double? elevation; double? _elevation;
Color? backgroundColor; Color? _backgroundColor;
MaterialStatesController? internalStatesController;
void handleStatesControllerChange() {
// Force a rebuild to resolve MaterialStateProperty properties
setState(() { });
}
MaterialStatesController get statesController => widget.statesController ?? internalStatesController!;
void initStatesController() {
if (widget.statesController == null) {
internalStatesController = MaterialStatesController();
}
statesController.update(MaterialState.disabled, !widget.enabled);
statesController.addListener(handleStatesControllerChange);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initStatesController(); setMaterialState(MaterialState.disabled, !widget.enabled);
} }
@override @override
void didUpdateWidget(ButtonStyleButton oldWidget) { void dispose() {
super.didUpdateWidget(oldWidget); _controller?.dispose();
if (widget.statesController != oldWidget.statesController) { super.dispose();
oldWidget.statesController?.removeListener(handleStatesControllerChange);
if (widget.statesController != null) {
internalStatesController?.dispose();
internalStatesController = null;
}
initStatesController();
}
if (widget.enabled != oldWidget.enabled) {
statesController.update(MaterialState.disabled, !widget.enabled);
if (!widget.enabled) {
// The button may have been disabled while a press gesture is currently underway.
statesController.update(MaterialState.pressed, false);
}
}
} }
@override @override
void dispose() { void didUpdateWidget(ButtonStyleButton oldWidget) {
statesController.removeListener(handleStatesControllerChange); super.didUpdateWidget(oldWidget);
internalStatesController?.dispose(); setMaterialState(MaterialState.disabled, !widget.enabled);
controller?.dispose(); // If the button is disabled while a press gesture is currently ongoing,
super.dispose(); // InkWell makes a call to handleHighlightChanged. This causes an exception
// because it calls setState in the middle of a build. To preempt this, we
// manually update pressed to false when this situation occurs.
if (isDisabled && isPressed) {
removeMaterialState(MaterialState.pressed);
}
} }
@override @override
...@@ -265,9 +237,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat ...@@ -265,9 +237,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) { T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) {
return effectiveValue( return effectiveValue(
(ButtonStyle? style) { (ButtonStyle? style) => getProperty(style)?.resolve(materialStates),
return getProperty(style)?.resolve(statesController.value);
},
); );
} }
...@@ -284,7 +254,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat ...@@ -284,7 +254,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side); final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side);
final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape); final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape);
final MaterialStateMouseCursor mouseCursor = _MouseCursor( final MaterialStateMouseCursor resolvedMouseCursor = _MouseCursor(
(Set<MaterialState> states) => effectiveValue((ButtonStyle? style) => style?.mouseCursor?.resolve(states)), (Set<MaterialState> states) => effectiveValue((ButtonStyle? style) => style?.mouseCursor?.resolve(states)),
); );
...@@ -339,16 +309,16 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat ...@@ -339,16 +309,16 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
// animates its elevation but not its color. SKIA renders non-zero // animates its elevation but not its color. SKIA renders non-zero
// elevations as a shadow colored fill behind the Material's background. // elevations as a shadow colored fill behind the Material's background.
if (resolvedAnimationDuration! > Duration.zero if (resolvedAnimationDuration! > Duration.zero
&& elevation != null && _elevation != null
&& backgroundColor != null && _backgroundColor != null
&& elevation != resolvedElevation && _elevation != resolvedElevation
&& backgroundColor!.value != resolvedBackgroundColor!.value && _backgroundColor!.value != resolvedBackgroundColor!.value
&& backgroundColor!.opacity == 1 && _backgroundColor!.opacity == 1
&& resolvedBackgroundColor.opacity < 1 && resolvedBackgroundColor.opacity < 1
&& resolvedElevation == 0) { && resolvedElevation == 0) {
if (controller?.duration != resolvedAnimationDuration) { if (_controller?.duration != resolvedAnimationDuration) {
controller?.dispose(); _controller?.dispose();
controller = AnimationController( _controller = AnimationController(
duration: resolvedAnimationDuration, duration: resolvedAnimationDuration,
vsync: this, vsync: this,
) )
...@@ -358,12 +328,12 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat ...@@ -358,12 +328,12 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
} }
}); });
} }
resolvedBackgroundColor = backgroundColor; // Defer changing the background color. resolvedBackgroundColor = _backgroundColor; // Defer changing the background color.
controller!.value = 0; _controller!.value = 0;
controller!.forward(); _controller!.forward();
} }
elevation = resolvedElevation; _elevation = resolvedElevation;
backgroundColor = resolvedBackgroundColor; _backgroundColor = resolvedBackgroundColor;
final Widget result = ConstrainedBox( final Widget result = ConstrainedBox(
constraints: effectiveConstraints, constraints: effectiveConstraints,
...@@ -380,18 +350,24 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat ...@@ -380,18 +350,24 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
child: InkWell( child: InkWell(
onTap: widget.onPressed, onTap: widget.onPressed,
onLongPress: widget.onLongPress, onLongPress: widget.onLongPress,
onHover: widget.onHover, onHighlightChanged: updateMaterialState(MaterialState.pressed),
mouseCursor: mouseCursor, onHover: updateMaterialState(
MaterialState.hovered,
onChanged: widget.onHover,
),
mouseCursor: resolvedMouseCursor,
enableFeedback: resolvedEnableFeedback, enableFeedback: resolvedEnableFeedback,
focusNode: widget.focusNode, focusNode: widget.focusNode,
canRequestFocus: widget.enabled, canRequestFocus: widget.enabled,
onFocusChange: widget.onFocusChange, onFocusChange: updateMaterialState(
MaterialState.focused,
onChanged: widget.onFocusChange,
),
autofocus: widget.autofocus, autofocus: widget.autofocus,
splashFactory: resolvedSplashFactory, splashFactory: resolvedSplashFactory,
overlayColor: overlayColor, overlayColor: overlayColor,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
customBorder: resolvedShape, customBorder: resolvedShape,
statesController: statesController,
child: IconTheme.merge( child: IconTheme.merge(
data: IconThemeData(color: resolvedForegroundColor), data: IconThemeData(color: resolvedForegroundColor),
child: Padding( child: Padding(
......
...@@ -71,7 +71,6 @@ class ElevatedButton extends ButtonStyleButton { ...@@ -71,7 +71,6 @@ class ElevatedButton extends ButtonStyleButton {
super.focusNode, super.focusNode,
super.autofocus = false, super.autofocus = false,
super.clipBehavior = Clip.none, super.clipBehavior = Clip.none,
super.statesController,
required super.child, required super.child,
}); });
......
...@@ -319,7 +319,6 @@ class InkResponse extends StatelessWidget { ...@@ -319,7 +319,6 @@ class InkResponse extends StatelessWidget {
this.canRequestFocus = true, this.canRequestFocus = true,
this.onFocusChange, this.onFocusChange,
this.autofocus = false, this.autofocus = false,
this.statesController,
}) : assert(containedInkWell != null), }) : assert(containedInkWell != null),
assert(highlightShape != null), assert(highlightShape != null),
assert(enableFeedback != null), assert(enableFeedback != null),
...@@ -582,19 +581,6 @@ class InkResponse extends StatelessWidget { ...@@ -582,19 +581,6 @@ class InkResponse extends StatelessWidget {
/// slightly more efficient). /// slightly more efficient).
RectCallback? getRectCallback(RenderBox referenceBox) => null; RectCallback? getRectCallback(RenderBox referenceBox) => null;
/// {@template flutter.material.inkwell.statesController}
/// Represents the interactive "state" of this widget in terms of
/// a set of [MaterialState]s, like [MaterialState.pressed] and
/// [MaterialState.focused].
///
/// Classes based on this one can provide their own
/// [MaterialStatesController] to which they've added listeners.
/// They can also update the controller's [MaterialStatesController.value]
/// however, this may only be done when it's safe to call
/// [State.setState], like in an event handler.
/// {@endtemplate}
final MaterialStatesController? statesController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context); final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context);
...@@ -628,7 +614,6 @@ class InkResponse extends StatelessWidget { ...@@ -628,7 +614,6 @@ class InkResponse extends StatelessWidget {
parentState: parentState, parentState: parentState,
getRectCallback: getRectCallback, getRectCallback: getRectCallback,
debugCheckContext: debugCheckContext, debugCheckContext: debugCheckContext,
statesController: statesController,
child: child, child: child,
); );
} }
...@@ -680,7 +665,6 @@ class _InkResponseStateWidget extends StatefulWidget { ...@@ -680,7 +665,6 @@ class _InkResponseStateWidget extends StatefulWidget {
this.parentState, this.parentState,
this.getRectCallback, this.getRectCallback,
required this.debugCheckContext, required this.debugCheckContext,
this.statesController,
}) : assert(containedInkWell != null), }) : assert(containedInkWell != null),
assert(highlightShape != null), assert(highlightShape != null),
assert(enableFeedback != null), assert(enableFeedback != null),
...@@ -718,7 +702,6 @@ class _InkResponseStateWidget extends StatefulWidget { ...@@ -718,7 +702,6 @@ class _InkResponseStateWidget extends StatefulWidget {
final _ParentInkResponseState? parentState; final _ParentInkResponseState? parentState;
final _GetRectCallback? getRectCallback; final _GetRectCallback? getRectCallback;
final _CheckContext debugCheckContext; final _CheckContext debugCheckContext;
final MaterialStatesController? statesController;
@override @override
_InkResponseState createState() => _InkResponseState(); _InkResponseState createState() => _InkResponseState();
...@@ -756,17 +739,15 @@ enum _HighlightType { ...@@ -756,17 +739,15 @@ enum _HighlightType {
class _InkResponseState extends State<_InkResponseStateWidget> class _InkResponseState extends State<_InkResponseStateWidget>
with AutomaticKeepAliveClientMixin<_InkResponseStateWidget> with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
implements _ParentInkResponseState implements _ParentInkResponseState {
{
Set<InteractiveInkFeature>? _splashes; Set<InteractiveInkFeature>? _splashes;
InteractiveInkFeature? _currentSplash; InteractiveInkFeature? _currentSplash;
bool _hovering = false; bool _hovering = false;
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{}; final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{ late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: simulateTap), ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _simulateTap),
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: simulateTap), ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: _simulateTap),
}; };
MaterialStatesController? internalStatesController;
bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty; bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty;
...@@ -788,65 +769,38 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -788,65 +769,38 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} }
bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty; bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;
void simulateTap([Intent? intent]) { void _simulateTap([Intent? intent]) {
_startNewSplash(context: context); _startNewSplash(context: context);
handleTap(); _handleTap();
} }
void simulateLongPress() { void _simulateLongPress() {
_startNewSplash(context: context); _startNewSplash(context: context);
handleLongPress(); _handleLongPress();
}
void handleStatesControllerChange() {
// Force a rebuild to resolve widget.overlayColor, widget.mouseCursor
setState(() { });
}
MaterialStatesController get statesController => widget.statesController ?? internalStatesController!;
void initStatesController() {
if (widget.statesController == null) {
internalStatesController = MaterialStatesController();
}
statesController.update(MaterialState.disabled, !enabled);
statesController.addListener(handleStatesControllerChange);
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initStatesController(); FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
FocusManager.instance.addHighlightModeListener(handleFocusHighlightModeChange);
} }
@override @override
void didUpdateWidget(_InkResponseStateWidget oldWidget) { void didUpdateWidget(_InkResponseStateWidget oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.statesController != oldWidget.statesController) { if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
oldWidget.statesController?.removeListener(handleStatesControllerChange); if (enabled) {
if (widget.statesController != null) {
internalStatesController?.dispose();
internalStatesController = null;
}
initStatesController();
}
if (enabled != isWidgetEnabled(oldWidget)) {
statesController.update(MaterialState.disabled, !enabled);
if (!enabled) {
statesController.update(MaterialState.pressed, false);
}
// Don't call widget.onHover because many widgets, including the button // Don't call widget.onHover because many widgets, including the button
// widgets, apply setState to an ancestor context from onHover. // widgets, apply setState to an ancestor context from onHover.
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false); updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
} }
updateFocusHighlights(); _updateFocusHighlights();
}
} }
@override @override
void dispose() { void dispose() {
FocusManager.instance.removeHighlightModeListener(handleFocusHighlightModeChange); FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
statesController.removeListener(handleStatesControllerChange);
super.dispose(); super.dispose();
} }
...@@ -854,18 +808,21 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -854,18 +808,21 @@ class _InkResponseState extends State<_InkResponseStateWidget>
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty); bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
Color getHighlightColorForType(_HighlightType type) { Color getHighlightColorForType(_HighlightType type) {
const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final Color? resolvedOverlayColor = widget.overlayColor?.resolve(statesController.value);
switch (type) { switch (type) {
// The pressed state triggers a ripple (ink splash), per the current // The pressed state triggers a ripple (ink splash), per the current
// Material Design spec. A separate highlight is no longer used. // Material Design spec. A separate highlight is no longer used.
// See https://material.io/design/interaction/states.html#pressed // See https://material.io/design/interaction/states.html#pressed
case _HighlightType.pressed: case _HighlightType.pressed:
return resolvedOverlayColor ?? widget.highlightColor ?? theme.highlightColor; return widget.overlayColor?.resolve(pressed) ?? widget.highlightColor ?? theme.highlightColor;
case _HighlightType.focus: case _HighlightType.focus:
return resolvedOverlayColor ?? widget.focusColor ?? theme.focusColor; return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? theme.focusColor;
case _HighlightType.hover: case _HighlightType.hover:
return resolvedOverlayColor ?? widget.hoverColor ?? theme.hoverColor; return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? theme.hoverColor;
} }
} }
...@@ -887,20 +844,6 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -887,20 +844,6 @@ class _InkResponseState extends State<_InkResponseStateWidget>
updateKeepAlive(); updateKeepAlive();
} }
switch (type) {
case _HighlightType.pressed:
statesController.update(MaterialState.pressed, value);
break;
case _HighlightType.hover:
if (callOnHover) {
statesController.update(MaterialState.hovered, value);
}
break;
case _HighlightType.focus:
// see handleFocusUpdate()
break;
}
if (type == _HighlightType.pressed) { if (type == _HighlightType.pressed) {
widget.parentState?.markChildInkResponsePressed(this, value); widget.parentState?.markChildInkResponsePressed(this, value);
} }
...@@ -950,7 +893,8 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -950,7 +893,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
final MaterialInkController inkController = Material.of(context)!; final MaterialInkController inkController = Material.of(context)!;
final RenderBox referenceBox = context.findRenderObject()! as RenderBox; final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
final Offset position = referenceBox.globalToLocal(globalPosition); final Offset position = referenceBox.globalToLocal(globalPosition);
final Color color = widget.overlayColor?.resolve(statesController.value) ?? widget.splashColor ?? Theme.of(context).splashColor; const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
final Color color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
final RectCallback? rectCallback = widget.containedInkWell ? widget.getRectCallback!(referenceBox) : null; final RectCallback? rectCallback = widget.containedInkWell ? widget.getRectCallback!(referenceBox) : null;
final BorderRadius? borderRadius = widget.borderRadius; final BorderRadius? borderRadius = widget.borderRadius;
final ShapeBorder? customBorder = widget.customBorder; final ShapeBorder? customBorder = widget.customBorder;
...@@ -984,12 +928,12 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -984,12 +928,12 @@ class _InkResponseState extends State<_InkResponseStateWidget>
return splash; return splash;
} }
void handleFocusHighlightModeChange(FocusHighlightMode mode) { void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
if (!mounted) { if (!mounted) {
return; return;
} }
setState(() { setState(() {
updateFocusHighlights(); _updateFocusHighlights();
}); });
} }
...@@ -1003,7 +947,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1003,7 +947,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} }
} }
void updateFocusHighlights() { void _updateFocusHighlights() {
final bool showFocus; final bool showFocus;
switch (FocusManager.instance.highlightMode) { switch (FocusManager.instance.highlightMode) {
case FocusHighlightMode.touch: case FocusHighlightMode.touch:
...@@ -1017,18 +961,13 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1017,18 +961,13 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} }
bool _hasFocus = false; bool _hasFocus = false;
void handleFocusUpdate(bool hasFocus) { void _handleFocusUpdate(bool hasFocus) {
_hasFocus = hasFocus; _hasFocus = hasFocus;
// Set here rather than updateHighlight because this widget's _updateFocusHighlights();
// (MaterialState) states include MaterialState.focused if
// the InkWell _has_ the focus, rather than if it's showing
// the focus per FocusManager.instance.highlightMode.
statesController.update(MaterialState.focused, hasFocus);
updateFocusHighlights();
widget.onFocusChange?.call(hasFocus); widget.onFocusChange?.call(hasFocus);
} }
void handleTapDown(TapDownDetails details) { void _handleTapDown(TapDownDetails details) {
if (_anyChildInkResponsePressed) { if (_anyChildInkResponsePressed) {
return; return;
} }
...@@ -1036,7 +975,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1036,7 +975,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
widget.onTapDown?.call(details); widget.onTapDown?.call(details);
} }
void handleTapUp(TapUpDetails details) { void _handleTapUp(TapUpDetails details) {
widget.onTapUp?.call(details); widget.onTapUp?.call(details);
} }
...@@ -1051,7 +990,6 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1051,7 +990,6 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} else { } else {
globalPosition = details!.globalPosition; globalPosition = details!.globalPosition;
} }
statesController.update(MaterialState.pressed, true); // ... before creating the splash
final InteractiveInkFeature splash = _createInkFeature(globalPosition); final InteractiveInkFeature splash = _createInkFeature(globalPosition);
_splashes ??= HashSet<InteractiveInkFeature>(); _splashes ??= HashSet<InteractiveInkFeature>();
_splashes!.add(splash); _splashes!.add(splash);
...@@ -1061,7 +999,7 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1061,7 +999,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
updateHighlight(_HighlightType.pressed, value: true); updateHighlight(_HighlightType.pressed, value: true);
} }
void handleTap() { void _handleTap() {
_currentSplash?.confirm(); _currentSplash?.confirm();
_currentSplash = null; _currentSplash = null;
updateHighlight(_HighlightType.pressed, value: false); updateHighlight(_HighlightType.pressed, value: false);
...@@ -1073,21 +1011,21 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1073,21 +1011,21 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} }
} }
void handleTapCancel() { void _handleTapCancel() {
_currentSplash?.cancel(); _currentSplash?.cancel();
_currentSplash = null; _currentSplash = null;
widget.onTapCancel?.call(); widget.onTapCancel?.call();
updateHighlight(_HighlightType.pressed, value: false); updateHighlight(_HighlightType.pressed, value: false);
} }
void handleDoubleTap() { void _handleDoubleTap() {
_currentSplash?.confirm(); _currentSplash?.confirm();
_currentSplash = null; _currentSplash = null;
updateHighlight(_HighlightType.pressed, value: false); updateHighlight(_HighlightType.pressed, value: false);
widget.onDoubleTap?.call(); widget.onDoubleTap?.call();
} }
void handleLongPress() { void _handleLongPress() {
_currentSplash?.confirm(); _currentSplash?.confirm();
_currentSplash = null; _currentSplash = null;
if (widget.onLongPress != null) { if (widget.onLongPress != null) {
...@@ -1117,27 +1055,27 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1117,27 +1055,27 @@ class _InkResponseState extends State<_InkResponseStateWidget>
super.deactivate(); super.deactivate();
} }
bool isWidgetEnabled(_InkResponseStateWidget widget) { bool _isWidgetEnabled(_InkResponseStateWidget widget) {
return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null || widget.onTapDown != null; return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null || widget.onTapDown != null;
} }
bool get enabled => isWidgetEnabled(widget); bool get enabled => _isWidgetEnabled(widget);
void handleMouseEnter(PointerEnterEvent event) { void _handleMouseEnter(PointerEnterEvent event) {
_hovering = true; _hovering = true;
if (enabled) { if (enabled) {
handleHoverChange(); _handleHoverChange();
} }
} }
void handleMouseExit(PointerExitEvent event) { void _handleMouseExit(PointerExitEvent event) {
_hovering = false; _hovering = false;
// If the exit occurs after we've been disabled, we still // If the exit occurs after we've been disabled, we still
// want to take down the highlights and run widget.onHover. // want to take down the highlights and run widget.onHover.
handleHoverChange(); _handleHoverChange();
} }
void handleHoverChange() { void _handleHoverChange() {
updateHighlight(_HighlightType.hover, value: _hovering); updateHighlight(_HighlightType.hover, value: _hovering);
} }
...@@ -1159,11 +1097,16 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1159,11 +1097,16 @@ class _InkResponseState extends State<_InkResponseStateWidget>
_highlights[type]?.color = getHighlightColorForType(type); _highlights[type]?.color = getHighlightColorForType(type);
} }
_currentSplash?.color = widget.overlayColor?.resolve(statesController.value) ?? widget.splashColor ?? Theme.of(context).splashColor; const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
_currentSplash?.color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>( final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable, widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
statesController.value, <MaterialState>{
if (!enabled) MaterialState.disabled,
if (_hovering && enabled) MaterialState.hovered,
if (_hasFocus) MaterialState.focused,
},
); );
return _ParentInkResponseProvider( return _ParentInkResponseProvider(
...@@ -1173,22 +1116,22 @@ class _InkResponseState extends State<_InkResponseStateWidget> ...@@ -1173,22 +1116,22 @@ class _InkResponseState extends State<_InkResponseStateWidget>
child: Focus( child: Focus(
focusNode: widget.focusNode, focusNode: widget.focusNode,
canRequestFocus: _canRequestFocus, canRequestFocus: _canRequestFocus,
onFocusChange: handleFocusUpdate, onFocusChange: _handleFocusUpdate,
autofocus: widget.autofocus, autofocus: widget.autofocus,
child: MouseRegion( child: MouseRegion(
cursor: effectiveMouseCursor, cursor: effectiveMouseCursor,
onEnter: handleMouseEnter, onEnter: _handleMouseEnter,
onExit: handleMouseExit, onExit: _handleMouseExit,
child: Semantics( child: Semantics(
onTap: widget.excludeFromSemantics || widget.onTap == null ? null : simulateTap, onTap: widget.excludeFromSemantics || widget.onTap == null ? null : _simulateTap,
onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : simulateLongPress, onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : _simulateLongPress,
child: GestureDetector( child: GestureDetector(
onTapDown: enabled ? handleTapDown : null, onTapDown: enabled ? _handleTapDown : null,
onTapUp: enabled ? handleTapUp : null, onTapUp: enabled ? _handleTapUp : null,
onTap: enabled ? handleTap : null, onTap: enabled ? _handleTap : null,
onTapCancel: enabled ? handleTapCancel : null, onTapCancel: enabled ? _handleTapCancel : null,
onDoubleTap: widget.onDoubleTap != null ? handleDoubleTap : null, onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
onLongPress: widget.onLongPress != null ? handleLongPress : null, onLongPress: widget.onLongPress != null ? _handleLongPress : null,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
excludeFromSemantics: true, excludeFromSemantics: true,
child: widget.child, child: widget.child,
...@@ -1313,7 +1256,6 @@ class InkWell extends InkResponse { ...@@ -1313,7 +1256,6 @@ class InkWell extends InkResponse {
super.canRequestFocus, super.canRequestFocus,
super.onFocusChange, super.onFocusChange,
super.autofocus, super.autofocus,
super.statesController,
}) : super( }) : super(
containedInkWell: true, containedInkWell: true,
highlightShape: BoxShape.rectangle, highlightShape: BoxShape.rectangle,
......
...@@ -686,26 +686,3 @@ class MaterialStatePropertyAll<T> implements MaterialStateProperty<T> { ...@@ -686,26 +686,3 @@ class MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
@override @override
String toString() => 'MaterialStatePropertyAll($value)'; String toString() => 'MaterialStatePropertyAll($value)';
} }
/// Manages a set of [MaterialState]s and notifies listeners of changes.
///
/// Used by widgets that expose their internal state for the sake of
/// extensions that add support for additional states. See
/// [TextButton.statesController] for example.
///
/// The controller's [value] is its current set of states. Listeners
/// are notified whenever the [value] changes. The [value] should only be
/// changed with [update]; it should not be modified directly.
class MaterialStatesController extends ValueNotifier<Set<MaterialState>> {
/// Creates a MaterialStatesController.
MaterialStatesController([Set<MaterialState>? value]) : super(<MaterialState>{...?value});
/// Adds [state] to [value] if [add] is true, and removes it otherwise,
/// and notifies listeners if [value] has changed.
void update(MaterialState state, bool add) {
final bool valueChanged = add ? value.add(state) : value.remove(state);
if (valueChanged) {
notifyListeners();
}
}
}
...@@ -76,7 +76,6 @@ class OutlinedButton extends ButtonStyleButton { ...@@ -76,7 +76,6 @@ class OutlinedButton extends ButtonStyleButton {
super.focusNode, super.focusNode,
super.autofocus = false, super.autofocus = false,
super.clipBehavior = Clip.none, super.clipBehavior = Clip.none,
super.statesController,
required Widget super.child, required Widget super.child,
}); });
......
...@@ -57,13 +57,6 @@ import 'theme_data.dart'; ...@@ -57,13 +57,6 @@ import 'theme_data.dart';
/// ** See code in examples/api/lib/material/text_button/text_button.0.dart ** /// ** See code in examples/api/lib/material/text_button/text_button.0.dart **
/// {@end-tool} /// {@end-tool}
/// ///
/// {@tool dartpad}
/// This sample demonstrates using the [statesController] parameter to create a button
/// that adds support for [MaterialState.selected].
///
/// ** See code in examples/api/lib/material/text_button/text_button.1.dart **
/// {@end-tool}
///
/// See also: /// See also:
/// ///
/// * [OutlinedButton], a [TextButton] with a border outline. /// * [OutlinedButton], a [TextButton] with a border outline.
...@@ -83,7 +76,6 @@ class TextButton extends ButtonStyleButton { ...@@ -83,7 +76,6 @@ class TextButton extends ButtonStyleButton {
super.focusNode, super.focusNode,
super.autofocus = false, super.autofocus = false,
super.clipBehavior = Clip.none, super.clipBehavior = Clip.none,
super.statesController,
required Widget super.child, required Widget super.child,
}); });
......
...@@ -1554,123 +1554,6 @@ void main() { ...@@ -1554,123 +1554,6 @@ void main() {
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
}); });
testWidgets('ElevatedButton statesController', (WidgetTester tester) async {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: ElevatedButton(
statesController: controller,
onPressed: () { },
child: const Text('button'),
),
),
),
);
expect(controller.value, <MaterialState>{});
expect(count, 0);
final Offset center = tester.getCenter(find.byType(ElevatedButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 1);
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{});
expect(count, 2);
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 3);
await gesture.down(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 4);
await gesture.up();
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 5);
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{});
expect(count, 6);
await gesture.down(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 8); // adds hovered and pressed - two changes
// If the button is rebuilt disabled, then the pressed state is
// removed.
await tester.pumpWidget(
MaterialApp(
home: Center(
child: ElevatedButton(
statesController: controller,
onPressed: null,
child: const Text('button'),
),
),
),
);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.disabled});
expect(count, 10); // removes pressed and adds disabled - two changes
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.disabled});
expect(count, 11);
await gesture.removePointer();
});
testWidgets('Disabled ElevatedButton statesController', (WidgetTester tester) async {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: ElevatedButton(
statesController: controller,
onPressed: null,
child: const Text('button'),
),
),
),
);
expect(controller.value, <MaterialState>{MaterialState.disabled});
expect(count, 1);
});
} }
TextStyle _iconStyle(WidgetTester tester, IconData icon) { TextStyle _iconStyle(WidgetTester tester, IconData icon) {
......
...@@ -1513,47 +1513,4 @@ void main() { ...@@ -1513,47 +1513,4 @@ void main() {
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0)); expect(inkFeatures, paintsExactlyCountTimes(#drawCircle, 0));
}); });
testWidgets('InkWell dispose statesController', (WidgetTester tester) async {
int tapCount = 0;
Widget buildFrame(MaterialStatesController? statesController) {
return MaterialApp(
home: Scaffold(
body: Center(
child: InkWell(
statesController: statesController,
onTap: () { tapCount += 1; },
child: const Text('inkwell'),
),
),
),
);
}
final MaterialStatesController controller = MaterialStatesController();
int pressedCount = 0;
controller.addListener(() {
if (controller.value.contains(MaterialState.pressed)) {
pressedCount += 1;
}
});
await tester.pumpWidget(buildFrame(controller));
await tester.tap(find.byType(InkWell));
await tester.pumpAndSettle();
expect(tapCount, 1);
expect(pressedCount, 1);
await tester.pumpWidget(buildFrame(null));
await tester.tap(find.byType(InkWell));
await tester.pumpAndSettle();
expect(tapCount, 2);
expect(pressedCount, 1);
await tester.pumpWidget(buildFrame(controller));
await tester.tap(find.byType(InkWell));
await tester.pumpAndSettle();
expect(tapCount, 3);
expect(pressedCount, 2);
});
} }
// 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 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('MaterialStatesController constructor', () {
expect(MaterialStatesController().value, <MaterialState>{});
expect(MaterialStatesController(<MaterialState>{}).value, <MaterialState>{});
expect(MaterialStatesController(<MaterialState>{MaterialState.selected}).value, <MaterialState>{MaterialState.selected});
});
test('MaterialStatesController update, listener', () {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
controller.update(MaterialState.selected, true);
expect(controller.value, <MaterialState>{MaterialState.selected});
expect(count, 1);
controller.update(MaterialState.selected, true);
expect(controller.value, <MaterialState>{MaterialState.selected});
expect(count, 1);
controller.update(MaterialState.hovered, false);
expect(count, 1);
expect(controller.value, <MaterialState>{MaterialState.selected});
controller.update(MaterialState.selected, false);
expect(count, 2);
expect(controller.value, <MaterialState>{});
controller.update(MaterialState.hovered, true);
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 3);
controller.update(MaterialState.hovered, true);
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 3);
controller.update(MaterialState.pressed, true);
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 4);
controller.update(MaterialState.selected, true);
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed, MaterialState.selected});
expect(count, 5);
controller.update(MaterialState.selected, false);
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 6);
controller.update(MaterialState.selected, false);
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 6);
controller.update(MaterialState.pressed, false);
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 7);
controller.update(MaterialState.hovered, false);
expect(controller.value, <MaterialState>{});
expect(count, 8);
controller.removeListener(valueChanged);
controller.update(MaterialState.selected, true);
expect(controller.value, <MaterialState>{MaterialState.selected});
expect(count, 8);
});
test('MaterialStatesController const initial value', () {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController(const <MaterialState>{MaterialState.selected});
controller.addListener(valueChanged);
controller.update(MaterialState.selected, true);
expect(controller.value, <MaterialState>{MaterialState.selected});
expect(count, 0);
controller.update(MaterialState.selected, false);
expect(controller.value, <MaterialState>{});
expect(count, 1);
});
}
...@@ -1717,123 +1717,6 @@ void main() { ...@@ -1717,123 +1717,6 @@ void main() {
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
}); });
testWidgets('OutlinedButton statesController', (WidgetTester tester) async {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: OutlinedButton(
statesController: controller,
onPressed: () { },
child: const Text('button'),
),
),
),
);
expect(controller.value, <MaterialState>{});
expect(count, 0);
final Offset center = tester.getCenter(find.byType(OutlinedButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 1);
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{});
expect(count, 2);
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 3);
await gesture.down(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 4);
await gesture.up();
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 5);
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{});
expect(count, 6);
await gesture.down(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 8); // adds hovered and pressed - two changes
// If the button is rebuilt disabled, then the pressed state is
// removed.
await tester.pumpWidget(
MaterialApp(
home: Center(
child: OutlinedButton(
statesController: controller,
onPressed: null,
child: const Text('button'),
),
),
),
);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.disabled});
expect(count, 10); // removes pressed and adds disabled - two changes
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.disabled});
expect(count, 11);
await gesture.removePointer();
});
testWidgets('Disabled OutlinedButton statesController', (WidgetTester tester) async {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: OutlinedButton(
statesController: controller,
onPressed: null,
child: const Text('button'),
),
),
),
);
expect(controller.value, <MaterialState>{MaterialState.disabled});
expect(count, 1);
});
} }
TextStyle _iconStyle(WidgetTester tester, IconData icon) { TextStyle _iconStyle(WidgetTester tester, IconData icon) {
......
...@@ -441,6 +441,7 @@ void main() { ...@@ -441,6 +441,7 @@ void main() {
), ),
); );
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(TextButton))); await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
...@@ -1524,123 +1525,6 @@ void main() { ...@@ -1524,123 +1525,6 @@ void main() {
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
}); });
testWidgets('TextButton statesController', (WidgetTester tester) async {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: TextButton(
statesController: controller,
onPressed: () { },
child: const Text('button'),
),
),
),
);
expect(controller.value, <MaterialState>{});
expect(count, 0);
final Offset center = tester.getCenter(find.byType(TextButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 1);
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{});
expect(count, 2);
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 3);
await gesture.down(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 4);
await gesture.up();
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered});
expect(count, 5);
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{});
expect(count, 6);
await gesture.down(center);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.pressed});
expect(count, 8); // adds hovered and pressed - two changes
// If the button is rebuilt disabled, then the pressed state is
// removed.
await tester.pumpWidget(
MaterialApp(
home: Center(
child: TextButton(
statesController: controller,
onPressed: null,
child: const Text('button'),
),
),
),
);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.hovered, MaterialState.disabled});
expect(count, 10); // removes pressed and adds disabled - two changes
await gesture.moveTo(Offset.zero);
await tester.pumpAndSettle();
expect(controller.value, <MaterialState>{MaterialState.disabled});
expect(count, 11);
await gesture.removePointer();
});
testWidgets('Disabled TextButton statesController', (WidgetTester tester) async {
int count = 0;
void valueChanged() {
count += 1;
}
final MaterialStatesController controller = MaterialStatesController();
controller.addListener(valueChanged);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: TextButton(
statesController: controller,
onPressed: null,
child: const Text('button'),
),
),
),
);
expect(controller.value, <MaterialState>{MaterialState.disabled});
expect(count, 1);
});
} }
TextStyle? _iconStyle(WidgetTester tester, IconData icon) { TextStyle? _iconStyle(WidgetTester tester, IconData icon) {
......
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