Unverified Commit 180566f2 authored by Hans Muller's avatar Hans Muller Committed by GitHub

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

parent df52b510
// 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,7 +14,6 @@ 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.
......@@ -39,6 +38,7 @@ 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,6 +95,9 @@ 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;
......@@ -191,36 +194,61 @@ 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 MaterialStateMixin, TickerProviderStateMixin {
AnimationController? _controller;
double? _elevation;
Color? _backgroundColor;
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(() { });
}
@override
void initState() {
super.initState();
setMaterialState(MaterialState.disabled, !widget.enabled);
MaterialStatesController get statesController => widget.statesController ?? internalStatesController!;
void initStatesController() {
if (widget.statesController == null) {
internalStatesController = MaterialStatesController();
}
statesController.update(MaterialState.disabled, !widget.enabled);
statesController.addListener(handleStatesControllerChange);
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
void initState() {
super.initState();
initStatesController();
}
@override
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);
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);
}
}
}
@override
void dispose() {
statesController.removeListener(handleStatesControllerChange);
internalStatesController?.dispose();
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final ButtonStyle? widgetStyle = widget.style;
......@@ -237,7 +265,9 @@ class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin
T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) {
return effectiveValue(
(ButtonStyle? style) => getProperty(style)?.resolve(materialStates),
(ButtonStyle? style) {
return getProperty(style)?.resolve(statesController.value);
},
);
}
......@@ -254,7 +284,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin
final BorderSide? resolvedSide = resolve<BorderSide?>((ButtonStyle? style) => style?.side);
final OutlinedBorder? resolvedShape = resolve<OutlinedBorder?>((ButtonStyle? style) => style?.shape);
final MaterialStateMouseCursor resolvedMouseCursor = _MouseCursor(
final MaterialStateMouseCursor mouseCursor = _MouseCursor(
(Set<MaterialState> states) => effectiveValue((ButtonStyle? style) => style?.mouseCursor?.resolve(states)),
);
......@@ -309,16 +339,16 @@ class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin
// 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,
)
......@@ -328,12 +358,12 @@ class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin
}
});
}
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,
......@@ -350,24 +380,18 @@ class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin
child: InkWell(
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
onHighlightChanged: updateMaterialState(MaterialState.pressed),
onHover: updateMaterialState(
MaterialState.hovered,
onChanged: widget.onHover,
),
mouseCursor: resolvedMouseCursor,
onHover: widget.onHover,
mouseCursor: mouseCursor,
enableFeedback: resolvedEnableFeedback,
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
onFocusChange: updateMaterialState(
MaterialState.focused,
onChanged: widget.onFocusChange,
),
onFocusChange: 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,6 +71,7 @@ class ElevatedButton extends ButtonStyleButton {
super.focusNode,
super.autofocus = false,
super.clipBehavior = Clip.none,
super.statesController,
required super.child,
});
......
......@@ -686,3 +686,26 @@ 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,6 +76,7 @@ class OutlinedButton extends ButtonStyleButton {
super.focusNode,
super.autofocus = false,
super.clipBehavior = Clip.none,
super.statesController,
required Widget super.child,
});
......
......@@ -57,6 +57,13 @@ 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.
......@@ -76,6 +83,7 @@ class TextButton extends ButtonStyleButton {
super.focusNode,
super.autofocus = false,
super.clipBehavior = Clip.none,
super.statesController,
required Widget super.child,
});
......
......@@ -1554,6 +1554,123 @@ 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,4 +1513,47 @@ 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,6 +1717,123 @@ 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,7 +441,6 @@ void main() {
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
......@@ -1525,6 +1524,123 @@ 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