This reverts commit 180566f2e5.
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
// 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'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// 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 dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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();
|
||||
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);
|
||||
}
|
||||
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
|
||||
void dispose() {
|
||||
statesController.removeListener(handleStatesControllerChange);
|
||||
internalStatesController?.dispose();
|
||||
controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// 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) {
|
||||
|
||||
Reference in New Issue
Block a user