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';
import 'ink_well.dart';
import 'material.dart';
import 'material_state.dart';
import 'material_state_mixin.dart';
import 'theme_data.dart';
/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
......@@ -38,7 +39,6 @@ abstract class ButtonStyleButton extends StatefulWidget {
required this.focusNode,
required this.autofocus,
required this.clipBehavior,
this.statesController,
required this.child,
}) : assert(autofocus != null),
assert(clipBehavior != null);
......@@ -95,9 +95,6 @@ abstract class ButtonStyleButton extends StatefulWidget {
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// {@macro flutter.material.inkwell.statesController}
final MaterialStatesController? statesController;
/// Typically the button's label.
final Widget? child;
......@@ -194,59 +191,34 @@ abstract class ButtonStyleButton extends StatefulWidget {
/// * [TextButton], a simple button without a shadow.
/// * [ElevatedButton], a filled button whose material elevates when pressed.
/// * [OutlinedButton], similar to [TextButton], but with an outline.
class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStateMixin {
AnimationController? controller;
double? elevation;
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);
}
class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin, TickerProviderStateMixin {
AnimationController? _controller;
double? _elevation;
Color? _backgroundColor;
@override
void initState() {
super.initState();
initStatesController();
setMaterialState(MaterialState.disabled, !widget.enabled);
}
@override
void didUpdateWidget(ButtonStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.statesController != oldWidget.statesController) {
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);
}
}
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
void dispose() {
statesController.removeListener(handleStatesControllerChange);
internalStatesController?.dispose();
controller?.dispose();
super.dispose();
void didUpdateWidget(ButtonStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
setMaterialState(MaterialState.disabled, !widget.enabled);
// If the button is disabled while a press gesture is currently ongoing,
// 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
......@@ -265,9 +237,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) {
return effectiveValue(
(ButtonStyle? style) {
return getProperty(style)?.resolve(statesController.value);
},
(ButtonStyle? style) => getProperty(style)?.resolve(materialStates),
);
}
......@@ -284,7 +254,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side);
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)),
);
......@@ -339,16 +309,16 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
// animates its elevation but not its color. SKIA renders non-zero
// elevations as a shadow colored fill behind the Material's background.
if (resolvedAnimationDuration! > Duration.zero
&& elevation != null
&& backgroundColor != null
&& elevation != resolvedElevation
&& backgroundColor!.value != resolvedBackgroundColor!.value
&& backgroundColor!.opacity == 1
&& _elevation != null
&& _backgroundColor != null
&& _elevation != resolvedElevation
&& _backgroundColor!.value != resolvedBackgroundColor!.value
&& _backgroundColor!.opacity == 1
&& resolvedBackgroundColor.opacity < 1
&& resolvedElevation == 0) {
if (controller?.duration != resolvedAnimationDuration) {
controller?.dispose();
controller = AnimationController(
if (_controller?.duration != resolvedAnimationDuration) {
_controller?.dispose();
_controller = AnimationController(
duration: resolvedAnimationDuration,
vsync: this,
)
......@@ -358,12 +328,12 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
}
});
}
resolvedBackgroundColor = backgroundColor; // Defer changing the background color.
controller!.value = 0;
controller!.forward();
resolvedBackgroundColor = _backgroundColor; // Defer changing the background color.
_controller!.value = 0;
_controller!.forward();
}
elevation = resolvedElevation;
backgroundColor = resolvedBackgroundColor;
_elevation = resolvedElevation;
_backgroundColor = resolvedBackgroundColor;
final Widget result = ConstrainedBox(
constraints: effectiveConstraints,
......@@ -380,18 +350,24 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
child: InkWell(
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
onHover: widget.onHover,
mouseCursor: mouseCursor,
onHighlightChanged: updateMaterialState(MaterialState.pressed),
onHover: updateMaterialState(
MaterialState.hovered,
onChanged: widget.onHover,
),
mouseCursor: resolvedMouseCursor,
enableFeedback: resolvedEnableFeedback,
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
onFocusChange: widget.onFocusChange,
onFocusChange: updateMaterialState(
MaterialState.focused,
onChanged: widget.onFocusChange,
),
autofocus: widget.autofocus,
splashFactory: resolvedSplashFactory,
overlayColor: overlayColor,
highlightColor: Colors.transparent,
customBorder: resolvedShape,
statesController: statesController,
child: IconTheme.merge(
data: IconThemeData(color: resolvedForegroundColor),
child: Padding(
......
......@@ -71,7 +71,6 @@ class ElevatedButton extends ButtonStyleButton {
super.focusNode,
super.autofocus = false,
super.clipBehavior = Clip.none,
super.statesController,
required super.child,
});
......
......@@ -319,7 +319,6 @@ class InkResponse extends StatelessWidget {
this.canRequestFocus = true,
this.onFocusChange,
this.autofocus = false,
this.statesController,
}) : assert(containedInkWell != null),
assert(highlightShape != null),
assert(enableFeedback != null),
......@@ -582,19 +581,6 @@ class InkResponse extends StatelessWidget {
/// slightly more efficient).
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
Widget build(BuildContext context) {
final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context);
......@@ -628,7 +614,6 @@ class InkResponse extends StatelessWidget {
parentState: parentState,
getRectCallback: getRectCallback,
debugCheckContext: debugCheckContext,
statesController: statesController,
child: child,
);
}
......@@ -680,7 +665,6 @@ class _InkResponseStateWidget extends StatefulWidget {
this.parentState,
this.getRectCallback,
required this.debugCheckContext,
this.statesController,
}) : assert(containedInkWell != null),
assert(highlightShape != null),
assert(enableFeedback != null),
......@@ -718,7 +702,6 @@ class _InkResponseStateWidget extends StatefulWidget {
final _ParentInkResponseState? parentState;
final _GetRectCallback? getRectCallback;
final _CheckContext debugCheckContext;
final MaterialStatesController? statesController;
@override
_InkResponseState createState() => _InkResponseState();
......@@ -755,18 +738,16 @@ enum _HighlightType {
}
class _InkResponseState extends State<_InkResponseStateWidget>
with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
implements _ParentInkResponseState
{
with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
implements _ParentInkResponseState {
Set<InteractiveInkFeature>? _splashes;
InteractiveInkFeature? _currentSplash;
bool _hovering = false;
final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: simulateTap),
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: simulateTap),
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _simulateTap),
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: _simulateTap),
};
MaterialStatesController? internalStatesController;
bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty;
......@@ -788,65 +769,38 @@ class _InkResponseState extends State<_InkResponseStateWidget>
}
bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;
void simulateTap([Intent? intent]) {
void _simulateTap([Intent? intent]) {
_startNewSplash(context: context);
handleTap();
_handleTap();
}
void simulateLongPress() {
void _simulateLongPress() {
_startNewSplash(context: context);
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);
_handleLongPress();
}
@override
void initState() {
super.initState();
initStatesController();
FocusManager.instance.addHighlightModeListener(handleFocusHighlightModeChange);
FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
}
@override
void didUpdateWidget(_InkResponseStateWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.statesController != oldWidget.statesController) {
oldWidget.statesController?.removeListener(handleStatesControllerChange);
if (widget.statesController != null) {
internalStatesController?.dispose();
internalStatesController = null;
if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
if (enabled) {
// Don't call widget.onHover because many widgets, including the button
// widgets, apply setState to an ancestor context from onHover.
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
}
initStatesController();
_updateFocusHighlights();
}
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
// widgets, apply setState to an ancestor context from onHover.
updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
}
updateFocusHighlights();
}
@override
void dispose() {
FocusManager.instance.removeHighlightModeListener(handleFocusHighlightModeChange);
statesController.removeListener(handleStatesControllerChange);
FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
super.dispose();
}
......@@ -854,18 +808,21 @@ class _InkResponseState extends State<_InkResponseStateWidget>
bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
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 Color? resolvedOverlayColor = widget.overlayColor?.resolve(statesController.value);
switch (type) {
// The pressed state triggers a ripple (ink splash), per the current
// Material Design spec. A separate highlight is no longer used.
// See https://material.io/design/interaction/states.html#pressed
case _HighlightType.pressed:
return resolvedOverlayColor ?? widget.highlightColor ?? theme.highlightColor;
return widget.overlayColor?.resolve(pressed) ?? widget.highlightColor ?? theme.highlightColor;
case _HighlightType.focus:
return resolvedOverlayColor ?? widget.focusColor ?? theme.focusColor;
return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? theme.focusColor;
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>
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) {
widget.parentState?.markChildInkResponsePressed(this, value);
}
......@@ -950,7 +893,8 @@ class _InkResponseState extends State<_InkResponseStateWidget>
final MaterialInkController inkController = Material.of(context)!;
final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
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 BorderRadius? borderRadius = widget.borderRadius;
final ShapeBorder? customBorder = widget.customBorder;
......@@ -984,12 +928,12 @@ class _InkResponseState extends State<_InkResponseStateWidget>
return splash;
}
void handleFocusHighlightModeChange(FocusHighlightMode mode) {
void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
if (!mounted) {
return;
}
setState(() {
updateFocusHighlights();
_updateFocusHighlights();
});
}
......@@ -1003,7 +947,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
}
}
void updateFocusHighlights() {
void _updateFocusHighlights() {
final bool showFocus;
switch (FocusManager.instance.highlightMode) {
case FocusHighlightMode.touch:
......@@ -1017,18 +961,13 @@ class _InkResponseState extends State<_InkResponseStateWidget>
}
bool _hasFocus = false;
void handleFocusUpdate(bool hasFocus) {
void _handleFocusUpdate(bool hasFocus) {
_hasFocus = hasFocus;
// Set here rather than updateHighlight because this widget's
// (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();
_updateFocusHighlights();
widget.onFocusChange?.call(hasFocus);
}
void handleTapDown(TapDownDetails details) {
void _handleTapDown(TapDownDetails details) {
if (_anyChildInkResponsePressed) {
return;
}
......@@ -1036,7 +975,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
widget.onTapDown?.call(details);
}
void handleTapUp(TapUpDetails details) {
void _handleTapUp(TapUpDetails details) {
widget.onTapUp?.call(details);
}
......@@ -1051,7 +990,6 @@ class _InkResponseState extends State<_InkResponseStateWidget>
} else {
globalPosition = details!.globalPosition;
}
statesController.update(MaterialState.pressed, true); // ... before creating the splash
final InteractiveInkFeature splash = _createInkFeature(globalPosition);
_splashes ??= HashSet<InteractiveInkFeature>();
_splashes!.add(splash);
......@@ -1061,7 +999,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
updateHighlight(_HighlightType.pressed, value: true);
}
void handleTap() {
void _handleTap() {
_currentSplash?.confirm();
_currentSplash = null;
updateHighlight(_HighlightType.pressed, value: false);
......@@ -1073,21 +1011,21 @@ class _InkResponseState extends State<_InkResponseStateWidget>
}
}
void handleTapCancel() {
void _handleTapCancel() {
_currentSplash?.cancel();
_currentSplash = null;
widget.onTapCancel?.call();
updateHighlight(_HighlightType.pressed, value: false);
}
void handleDoubleTap() {
void _handleDoubleTap() {
_currentSplash?.confirm();
_currentSplash = null;
updateHighlight(_HighlightType.pressed, value: false);
widget.onDoubleTap?.call();
}
void handleLongPress() {
void _handleLongPress() {
_currentSplash?.confirm();
_currentSplash = null;
if (widget.onLongPress != null) {
......@@ -1117,27 +1055,27 @@ class _InkResponseState extends State<_InkResponseStateWidget>
super.deactivate();
}
bool isWidgetEnabled(_InkResponseStateWidget widget) {
bool _isWidgetEnabled(_InkResponseStateWidget widget) {
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;
if (enabled) {
handleHoverChange();
_handleHoverChange();
}
}
void handleMouseExit(PointerExitEvent event) {
void _handleMouseExit(PointerExitEvent event) {
_hovering = false;
// If the exit occurs after we've been disabled, we still
// want to take down the highlights and run widget.onHover.
handleHoverChange();
_handleHoverChange();
}
void handleHoverChange() {
void _handleHoverChange() {
updateHighlight(_HighlightType.hover, value: _hovering);
}
......@@ -1159,11 +1097,16 @@ class _InkResponseState extends State<_InkResponseStateWidget>
_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>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
statesController.value,
<MaterialState>{
if (!enabled) MaterialState.disabled,
if (_hovering && enabled) MaterialState.hovered,
if (_hasFocus) MaterialState.focused,
},
);
return _ParentInkResponseProvider(
......@@ -1173,22 +1116,22 @@ class _InkResponseState extends State<_InkResponseStateWidget>
child: Focus(
focusNode: widget.focusNode,
canRequestFocus: _canRequestFocus,
onFocusChange: handleFocusUpdate,
onFocusChange: _handleFocusUpdate,
autofocus: widget.autofocus,
child: MouseRegion(
cursor: effectiveMouseCursor,
onEnter: handleMouseEnter,
onExit: handleMouseExit,
onEnter: _handleMouseEnter,
onExit: _handleMouseExit,
child: Semantics(
onTap: widget.excludeFromSemantics || widget.onTap == null ? null : simulateTap,
onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : simulateLongPress,
onTap: widget.excludeFromSemantics || widget.onTap == null ? null : _simulateTap,
onLongPress: widget.excludeFromSemantics || widget.onLongPress == null ? null : _simulateLongPress,
child: GestureDetector(
onTapDown: enabled ? handleTapDown : null,
onTapUp: enabled ? handleTapUp : null,
onTap: enabled ? handleTap : null,
onTapCancel: enabled ? handleTapCancel : null,
onDoubleTap: widget.onDoubleTap != null ? handleDoubleTap : null,
onLongPress: widget.onLongPress != null ? handleLongPress : null,
onTapDown: enabled ? _handleTapDown : null,
onTapUp: enabled ? _handleTapUp : null,
onTap: enabled ? _handleTap : null,
onTapCancel: enabled ? _handleTapCancel : null,
onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
onLongPress: widget.onLongPress != null ? _handleLongPress : null,
behavior: HitTestBehavior.opaque,
excludeFromSemantics: true,
child: widget.child,
......@@ -1313,7 +1256,6 @@ class InkWell extends InkResponse {
super.canRequestFocus,
super.onFocusChange,
super.autofocus,
super.statesController,
}) : super(
containedInkWell: true,
highlightShape: BoxShape.rectangle,
......
......@@ -686,26 +686,3 @@ class MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
@override
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 {
super.focusNode,
super.autofocus = false,
super.clipBehavior = Clip.none,
super.statesController,
required Widget super.child,
});
......
......@@ -57,13 +57,6 @@ import 'theme_data.dart';
/// ** See code in examples/api/lib/material/text_button/text_button.0.dart **
/// {@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:
///
/// * [OutlinedButton], a [TextButton] with a border outline.
......@@ -83,7 +76,6 @@ class TextButton extends ButtonStyleButton {
super.focusNode,
super.autofocus = false,
super.clipBehavior = Clip.none,
super.statesController,
required Widget super.child,
});
......
......@@ -1554,123 +1554,6 @@ void main() {
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) {
......
......@@ -1513,47 +1513,4 @@ void main() {
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
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() {
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) {
......
......@@ -441,6 +441,7 @@ void main() {
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
......@@ -1524,123 +1525,6 @@ void main() {
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) {
......
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