Unverified Commit 02b46b0d authored by Craig Labenz's avatar Craig Labenz Committed by GitHub

refactor: Add MaterialStateMixin (#82843)

parent b5fc79f9
......@@ -96,6 +96,7 @@ export 'src/material/material.dart';
export 'src/material/material_button.dart';
export 'src/material/material_localizations.dart';
export 'src/material/material_state.dart';
export 'src/material/material_state_mixin.dart';
export 'src/material/mergeable_material.dart';
export 'src/material/navigation_rail.dart';
export 'src/material/navigation_rail_theme.dart';
......
......@@ -13,6 +13,7 @@ import 'constants.dart';
import 'ink_well.dart';
import 'material.dart';
import 'material_state.dart';
import 'material_state_mixin.dart';
import 'theme.dart';
import 'theme_data.dart';
......@@ -314,75 +315,40 @@ class RawMaterialButton extends StatefulWidget {
State<RawMaterialButton> createState() => _RawMaterialButtonState();
}
class _RawMaterialButtonState extends State<RawMaterialButton> {
final Set<MaterialState> _states = <MaterialState>{};
bool get _hovered => _states.contains(MaterialState.hovered);
bool get _focused => _states.contains(MaterialState.focused);
bool get _pressed => _states.contains(MaterialState.pressed);
bool get _disabled => _states.contains(MaterialState.disabled);
void _updateState(MaterialState state, bool value) {
value ? _states.add(state) : _states.remove(state);
}
void _handleHighlightChanged(bool value) {
if (_pressed != value) {
setState(() {
_updateState(MaterialState.pressed, value);
widget.onHighlightChanged?.call(value);
});
}
}
void _handleHoveredChanged(bool value) {
if (_hovered != value) {
setState(() {
_updateState(MaterialState.hovered, value);
});
}
}
void _handleFocusedChanged(bool value) {
if (_focused != value) {
setState(() {
_updateState(MaterialState.focused, value);
});
}
}
class _RawMaterialButtonState extends State<RawMaterialButton> with MaterialStateMixin {
@override
void initState() {
super.initState();
_updateState(MaterialState.disabled, !widget.enabled);
setMaterialState(MaterialState.disabled, !widget.enabled);
}
@override
void didUpdateWidget(RawMaterialButton oldWidget) {
super.didUpdateWidget(oldWidget);
_updateState(MaterialState.disabled, !widget.enabled);
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 (_disabled && _pressed) {
_handleHighlightChanged(false);
if (isDisabled && isPressed) {
removeMaterialState(MaterialState.pressed);
}
}
double get _effectiveElevation {
// These conditionals are in order of precedence, so be careful about
// reorganizing them.
if (_disabled) {
if (isDisabled) {
return widget.disabledElevation;
}
if (_pressed) {
if (isPressed) {
return widget.highlightElevation;
}
if (_hovered) {
if (isHovered) {
return widget.hoverElevation;
}
if (_focused) {
if (isFocused) {
return widget.focusElevation;
}
return widget.elevation;
......@@ -390,13 +356,13 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
@override
Widget build(BuildContext context) {
final Color? effectiveTextColor = MaterialStateProperty.resolveAs<Color?>(widget.textStyle?.color, _states);
final ShapeBorder? effectiveShape = MaterialStateProperty.resolveAs<ShapeBorder?>(widget.shape, _states);
final Color? effectiveTextColor = MaterialStateProperty.resolveAs<Color?>(widget.textStyle?.color, materialStates);
final ShapeBorder? effectiveShape = MaterialStateProperty.resolveAs<ShapeBorder?>(widget.shape, materialStates);
final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
final BoxConstraints effectiveConstraints = widget.visualDensity.effectiveConstraints(widget.constraints);
final MouseCursor? effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
_states,
materialStates,
);
final EdgeInsetsGeometry padding = widget.padding.add(
EdgeInsets.only(
......@@ -421,14 +387,14 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
child: InkWell(
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
onFocusChange: _handleFocusedChanged,
onFocusChange: updateMaterialState(MaterialState.focused),
autofocus: widget.autofocus,
onHighlightChanged: _handleHighlightChanged,
onHighlightChanged: updateMaterialState(MaterialState.pressed, onChanged: widget.onHighlightChanged),
splashColor: widget.splashColor,
highlightColor: widget.highlightColor,
focusColor: widget.focusColor,
hoverColor: widget.hoverColor,
onHover: _handleHoveredChanged,
onHover: updateMaterialState(MaterialState.hovered),
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
enableFeedback: widget.enableFeedback,
......
......@@ -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.
......@@ -176,49 +177,15 @@ 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 {
class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin, TickerProviderStateMixin {
AnimationController? _controller;
double? _elevation;
Color? _backgroundColor;
final Set<MaterialState> _states = <MaterialState>{};
bool get _hovered => _states.contains(MaterialState.hovered);
bool get _focused => _states.contains(MaterialState.focused);
bool get _pressed => _states.contains(MaterialState.pressed);
bool get _disabled => _states.contains(MaterialState.disabled);
void _updateState(MaterialState state, bool value) {
value ? _states.add(state) : _states.remove(state);
}
void _handleHighlightChanged(bool value) {
if (_pressed != value) {
setState(() {
_updateState(MaterialState.pressed, value);
});
}
}
void _handleHoveredChanged(bool value) {
if (_hovered != value) {
setState(() {
_updateState(MaterialState.hovered, value);
});
}
}
void _handleFocusedChanged(bool value) {
if (_focused != value) {
setState(() {
_updateState(MaterialState.focused, value);
});
}
}
@override
void initState() {
super.initState();
_updateState(MaterialState.disabled, !widget.enabled);
setMaterialState(MaterialState.disabled, !widget.enabled);
}
@override
......@@ -230,13 +197,13 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
@override
void didUpdateWidget(ButtonStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
_updateState(MaterialState.disabled, !widget.enabled);
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 (_disabled && _pressed) {
_handleHighlightChanged(false);
if (isDisabled && isPressed) {
removeMaterialState(MaterialState.pressed);
}
}
......@@ -256,7 +223,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
T? resolve<T>(MaterialStateProperty<T>? Function(ButtonStyle? style) getProperty) {
return effectiveValue(
(ButtonStyle? style) => getProperty(style)?.resolve(_states),
(ButtonStyle? style) => getProperty(style)?.resolve(materialStates),
);
}
......@@ -367,13 +334,13 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat
child: InkWell(
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
onHighlightChanged: _handleHighlightChanged,
onHover: _handleHoveredChanged,
onHighlightChanged: updateMaterialState(MaterialState.pressed),
onHover: updateMaterialState(MaterialState.hovered),
mouseCursor: resolvedMouseCursor,
enableFeedback: resolvedEnableFeedback,
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
onFocusChange: _handleFocusedChanged,
onFocusChange: updateMaterialState(MaterialState.focused),
autofocus: widget.autofocus,
splashFactory: resolvedSplashFactory,
overlayColor: overlayColor,
......
......@@ -17,6 +17,7 @@ import 'ink_well.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'material_state.dart';
import 'material_state_mixin.dart';
import 'theme.dart';
import 'theme_data.dart';
import 'tooltip.dart';
......@@ -1631,7 +1632,7 @@ class RawChip extends StatefulWidget
State<RawChip> createState() => _RawChipState();
}
class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip> {
class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProviderStateMixin<RawChip> {
static const Duration pressedAnimationDuration = Duration(milliseconds: 75);
late AnimationController selectController;
......@@ -1644,8 +1645,6 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
late Animation<double> enableAnimation;
late Animation<double> selectionFade;
final Set<MaterialState> _states = <MaterialState>{};
final GlobalKey deleteIconKey = GlobalKey();
bool get hasDeleteButton => widget.onDeleted != null;
......@@ -1664,8 +1663,8 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
void initState() {
assert(widget.onSelected == null || widget.onPressed == null);
super.initState();
_updateState(MaterialState.disabled, !widget.isEnabled);
_updateState(MaterialState.selected, widget.selected);
setMaterialState(MaterialState.disabled, !widget.isEnabled);
setMaterialState(MaterialState.selected, widget.selected);
selectController = AnimationController(
duration: _kSelectDuration,
value: widget.selected == true ? 1.0 : 0.0,
......@@ -1736,17 +1735,13 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
super.dispose();
}
void _updateState(MaterialState state, bool value) {
value ? _states.add(state) : _states.remove(state);
}
void _handleTapDown(TapDownDetails details) {
if (!canTap) {
return;
}
setMaterialState(MaterialState.pressed, true);
setState(() {
_isTapping = true;
_updateState(MaterialState.pressed, true);
});
}
......@@ -1754,9 +1749,9 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
if (!canTap) {
return;
}
setMaterialState(MaterialState.pressed, false);
setState(() {
_isTapping = false;
_updateState(MaterialState.pressed, false);
});
}
......@@ -1764,32 +1759,20 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
if (!canTap) {
return;
}
setMaterialState(MaterialState.pressed, false);
setState(() {
_isTapping = false;
_updateState(MaterialState.pressed, false);
});
// Only one of these can be set, so only one will be called.
widget.onSelected?.call(!widget.selected);
widget.onPressed?.call();
}
void _handleFocus(bool isFocused) {
setState(() {
_updateState(MaterialState.focused, isFocused);
});
}
void _handleHover(bool isHovered) {
setState(() {
_updateState(MaterialState.hovered, isHovered);
});
}
OutlinedBorder _getShape(ChipThemeData theme) {
final BorderSide? resolvedSide = MaterialStateProperty.resolveAs<BorderSide?>(widget.side, _states)
?? MaterialStateProperty.resolveAs<BorderSide?>(theme.side, _states);
final OutlinedBorder resolvedShape = MaterialStateProperty.resolveAs<OutlinedBorder?>(widget.shape, _states)
?? MaterialStateProperty.resolveAs<OutlinedBorder?>(theme.shape, _states)
final BorderSide? resolvedSide = MaterialStateProperty.resolveAs<BorderSide?>(widget.side, materialStates)
?? MaterialStateProperty.resolveAs<BorderSide?>(theme.side, materialStates);
final OutlinedBorder resolvedShape = MaterialStateProperty.resolveAs<OutlinedBorder?>(widget.shape, materialStates)
?? MaterialStateProperty.resolveAs<OutlinedBorder?>(theme.shape, materialStates)
?? const StadiumBorder();
return resolvedShape.copyWith(side: resolvedSide);
}
......@@ -1813,7 +1796,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
super.didUpdateWidget(oldWidget);
if (oldWidget.isEnabled != widget.isEnabled) {
setState(() {
_updateState(MaterialState.disabled, !widget.isEnabled);
setMaterialState(MaterialState.disabled, !widget.isEnabled);
if (widget.isEnabled) {
enableController.forward();
} else {
......@@ -1832,7 +1815,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
}
if (oldWidget.selected != widget.selected) {
setState(() {
_updateState(MaterialState.selected, widget.selected);
setMaterialState(MaterialState.selected, widget.selected);
if (widget.selected == true) {
selectController.forward();
} else {
......@@ -1932,7 +1915,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
final bool showCheckmark = widget.showCheckmark ?? chipTheme.showCheckmark ?? true;
final TextStyle effectiveLabelStyle = chipTheme.labelStyle.merge(widget.labelStyle);
final Color? resolvedLabelColor = MaterialStateProperty.resolveAs<Color?>(effectiveLabelStyle.color, _states);
final Color? resolvedLabelColor = MaterialStateProperty.resolveAs<Color?>(effectiveLabelStyle.color, materialStates);
final TextStyle resolvedLabelStyle = effectiveLabelStyle.copyWith(color: resolvedLabelColor);
final EdgeInsetsGeometry labelPadding = widget.labelPadding ?? chipTheme.labelPadding ?? _defaultLabelPadding;
......@@ -1943,14 +1926,14 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
shape: resolvedShape,
clipBehavior: widget.clipBehavior,
child: InkWell(
onFocusChange: _handleFocus,
onFocusChange: updateMaterialState(MaterialState.focused),
focusNode: widget.focusNode,
autofocus: widget.autofocus,
canRequestFocus: widget.isEnabled,
onTap: canTap ? _handleTap : null,
onTapDown: canTap ? _handleTapDown : null,
onTapCancel: canTap ? _handleTapCancel : null,
onHover: canTap ? _handleHover : null,
onHover: canTap ? updateMaterialState(MaterialState.hovered) : null,
splashFactory: _LocationAwareInkRippleFactory(
hasDeleteButton,
context,
......
// 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/foundation.dart';
import 'package:flutter/widgets.dart';
import 'material_state.dart';
/// Mixin for [State] classes that require knowledge of changing [MaterialState]
/// values for their child widgets.
///
/// This mixin does nothing by mere application to a [State] class, but is
/// helpful when writing `build` methods that include child [InkWell],
/// [GestureDetector], [MouseRegion], or [Focus] widgets. Instead of manually
/// creating handlers for each type of user interaction, such [State] classes can
/// instead provide a `ValueChanged<bool>` function and allow [MaterialStateMixin]
/// to manage the set of active [MaterialState]s, and the calling of [setState]
/// as necessary.
///
/// {@tool snippet}
/// This example shows how to write a [StatefulWidget] that uses the
/// [MaterialStateMixin] class to watch [MaterialState] values.
///
/// ```dart
/// class MyWidget extends StatefulWidget {
/// const MyWidget({required this.color, required this.child, Key? key}) : super(key: key);
///
/// final MaterialStateColor color;
/// final Widget child;
///
/// @override
/// State<MyWidget> createState() => MyWidgetState();
/// }
///
/// class MyWidgetState extends State<MyWidget> with MaterialStateMixin<MyWidget> {
/// @override
/// Widget build(BuildContext context) {
/// return InkWell(
/// onFocusChange: updateMaterialState(MaterialState.focused),
/// child: Container(
/// color: widget.color.resolve(materialStates),
/// child: widget.child,
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
@optionalTypeArgs
mixin MaterialStateMixin<T extends StatefulWidget> on State<T> {
/// Managed set of active [MaterialState] values; designed to be passed to
/// [MaterialStateProperty.resolve] methods.
///
/// To mutate and have [setState] called automatically for you, use
/// [setMaterialState], [addMaterialState], or [removeMaterialState]. Directly
/// mutating the set is possible, and may be necessary if you need to alter its
/// list without calling [setState] (and thus triggering a re-render).
///
/// To check for a single condition, convenience getters [isPressed], [isHovered],
/// [isFocused], etc, are available for each [MaterialState] value.
@protected
Set<MaterialState> materialStates = <MaterialState>{};
/// Callback factory which accepts a [MaterialState] value and returns a
/// closure to mutate [materialStates] and call [setState].
///
/// Accepts an optional second named parameter, `onChanged`, which allows
/// arbitrary functionality to be wired through the [MaterialStateMixin].
/// If supplied, the [onChanged] function is only called when child widgets
/// report events that make changes to the current set of [MaterialState]s.
///
/// {@tool snippet}
/// This example shows how to use the [updateMaterialState] callback factory
/// in other widgets, including the optional [onChanged] callback.
///
/// ```dart
/// class MyWidget extends StatefulWidget {
/// const MyWidget({this.onPressed, Key? key}) : super(key: key);
///
/// /// Something important this widget must do when pressed.
/// final VoidCallback? onPressed;
///
/// @override
/// State<MyWidget> createState() => MyWidgetState();
/// }
///
/// class MyWidgetState extends State<MyWidget> with MaterialStateMixin<MyWidget> {
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// color: isPressed ? Colors.black : Colors.white,
/// child: InkWell(
/// onHighlightChanged: updateMaterialState(
/// MaterialState.pressed,
/// onChanged: (bool val) {
/// if (val) {
/// widget.onPressed?.call();
/// }
/// },
/// ),
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
@protected
ValueChanged<bool> updateMaterialState(MaterialState key, {ValueChanged<bool>? onChanged}) {
return (bool value) {
if (materialStates.contains(key) == value)
return;
setMaterialState(key, value);
onChanged?.call(value);
};
}
/// Mutator to mark a [MaterialState] value as either active or inactive.
@protected
void setMaterialState(MaterialState _state, bool isSet) {
return isSet ? addMaterialState(_state) : removeMaterialState(_state);
}
/// Mutator to mark a [MaterialState] value as active.
@protected
void addMaterialState(MaterialState _state) {
if (materialStates.add(_state))
setState((){});
}
/// Mutator to mark a [MaterialState] value as inactive.
@protected
void removeMaterialState(MaterialState _state) {
if (materialStates.remove(_state))
setState((){});
}
/// Getter for whether this class considers [MaterialState.disabled] to be active.
bool get isDisabled => materialStates.contains(MaterialState.disabled);
/// Getter for whether this class considers [MaterialState.dragged] to be active.
bool get isDragged => materialStates.contains(MaterialState.dragged);
/// Getter for whether this class considers [MaterialState.error] to be active.
bool get isErrored => materialStates.contains(MaterialState.error);
/// Getter for whether this class considers [MaterialState.focused] to be active.
bool get isFocused => materialStates.contains(MaterialState.focused);
/// Getter for whether this class considers [MaterialState.hovered] to be active.
bool get isHovered => materialStates.contains(MaterialState.hovered);
/// Getter for whether this class considers [MaterialState.pressed] to be active.
bool get isPressed => materialStates.contains(MaterialState.pressed);
/// Getter for whether this class considers [MaterialState.scrolledUnder] to be active.
bool get isScrolledUnder => materialStates.contains(MaterialState.scrolledUnder);
/// Getter for whether this class considers [MaterialState.selected] to be active.
bool get isSelected => materialStates.contains(MaterialState.selected);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<Set<MaterialState>>('materialStates', materialStates, defaultValue: <MaterialState>{}));
}
}
// 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 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
const Key key = Key('testContainer');
const Color trueColor = Colors.red;
const Color falseColor = Colors.green;
/// Mock widget which plays the role of a button -- it can emit notifications
/// that [MaterialState] values are now in or out of play.
class _InnerWidget extends StatefulWidget {
const _InnerWidget({required this.onValueChanged, required this.controller, Key? key}) : super(key: key);
final ValueChanged<bool> onValueChanged;
final StreamController<bool> controller;
@override
_InnerWidgetState createState() => _InnerWidgetState();
}
class _InnerWidgetState extends State<_InnerWidget> {
@override
void initState() {
super.initState();
widget.controller.stream.listen((bool val) => widget.onValueChanged(val));
}
@override
Widget build(BuildContext context) => Container();
}
class _MyWidget extends StatefulWidget {
const _MyWidget({
required this.controller,
required this.evaluator,
required this.materialState,
Key? key,
}) : super(key: key);
/// Wrapper around `MaterialStateMixin.isPressed/isHovered/isFocused/etc`.
final bool Function(_MyWidgetState state) evaluator;
/// Stream passed down to the child [_InnerWidget] to begin the process.
/// This plays the role of an actual user interaction in the wild, but allows
/// us to engage the system without mocking pointers/hovers etc.
final StreamController<bool> controller;
/// The value we're watching in the given test.
final MaterialState materialState;
@override
State createState() => _MyWidgetState();
}
class _MyWidgetState extends State<_MyWidget> with MaterialStateMixin {
@override
Widget build(BuildContext context) {
return Container(
key: key,
color: widget.evaluator(this) ? trueColor : falseColor,
child: _InnerWidget(
onValueChanged: updateMaterialState(widget.materialState),
controller: widget.controller,
),
);
}
}
void main() {
Future<void> _verify(WidgetTester tester, Widget widget, StreamController<bool> controller,) async {
await tester.pumpWidget(MaterialApp(home: Scaffold(body: widget)));
// Set the value to True
controller.sink.add(true);
await tester.pumpAndSettle();
expect(tester.widget<Container>(find.byKey(key)).color, trueColor);
// Set the value to False
controller.sink.add(false);
await tester.pumpAndSettle();
expect(tester.widget<Container>(find.byKey(key)).color, falseColor);
}
testWidgets('MaterialState.pressed is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isPressed,
materialState: MaterialState.pressed,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.focused is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isFocused,
materialState: MaterialState.focused,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.hovered is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isHovered,
materialState: MaterialState.hovered,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.disabled is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isDisabled,
materialState: MaterialState.disabled,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.selected is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isSelected,
materialState: MaterialState.selected,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.scrolledUnder is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isScrolledUnder,
materialState: MaterialState.scrolledUnder,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.dragged is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isDragged,
materialState: MaterialState.dragged,
);
await _verify(tester, widget, controller);
});
testWidgets('MaterialState.error is tracked', (WidgetTester tester) async {
final StreamController<bool> controller = StreamController<bool>();
final _MyWidget widget = _MyWidget(
controller: controller,
evaluator: (_MyWidgetState state) => state.isErrored,
materialState: MaterialState.error,
);
await _verify(tester, widget, controller);
});
}
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