Unverified Commit b00b4e83 authored by Anthony's avatar Anthony Committed by GitHub

[Material] Add focus, highlight, and keyboard shortcuts to Slider (#53945)

parent 11ab2fa3
...@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart' show timeDilation; import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
...@@ -130,6 +131,8 @@ class Slider extends StatefulWidget { ...@@ -130,6 +131,8 @@ class Slider extends StatefulWidget {
this.activeColor, this.activeColor,
this.inactiveColor, this.inactiveColor,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.focusNode,
this.autofocus = false,
this.useV2Slider = false, this.useV2Slider = false,
}) : _sliderType = _SliderType.material, }) : _sliderType = _SliderType.material,
assert(value != null), assert(value != null),
...@@ -161,6 +164,8 @@ class Slider extends StatefulWidget { ...@@ -161,6 +164,8 @@ class Slider extends StatefulWidget {
this.activeColor, this.activeColor,
this.inactiveColor, this.inactiveColor,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.focusNode,
this.autofocus = false,
this.useV2Slider = false, this.useV2Slider = false,
}) : _sliderType = _SliderType.adaptive, }) : _sliderType = _SliderType.adaptive,
assert(value != null), assert(value != null),
...@@ -384,6 +389,12 @@ class Slider extends StatefulWidget { ...@@ -384,6 +389,12 @@ class Slider extends StatefulWidget {
/// Ignored if this slider is created with [Slider.adaptive] /// Ignored if this slider is created with [Slider.adaptive]
final SemanticFormatterCallback semanticFormatterCallback; final SemanticFormatterCallback semanticFormatterCallback;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// Whether to use the updated Material spec version of the [Slider]. /// Whether to use the updated Material spec version of the [Slider].
/// ///
/// * The v2 Slider has an updated value indicator that matches the latest specs. /// * The v2 Slider has an updated value indicator that matches the latest specs.
...@@ -416,8 +427,10 @@ class Slider extends StatefulWidget { ...@@ -416,8 +427,10 @@ class Slider extends StatefulWidget {
properties.add(StringProperty('label', label)); properties.add(StringProperty('label', label));
properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('inactiveColor', inactiveColor)); properties.add(ColorProperty('inactiveColor', inactiveColor));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback)); properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback));
properties.add(ObjectFlagProperty<FocusNode>.has('focusNode', focusNode));
properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'autofocus'));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
} }
} }
...@@ -437,6 +450,14 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -437,6 +450,14 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// and the next on a discrete slider. // and the next on a discrete slider.
AnimationController positionController; AnimationController positionController;
Timer interactionTimer; Timer interactionTimer;
final GlobalKey _renderObjectKey = GlobalKey();
// Keyboard mapping for a focused slider.
Map<LogicalKeySet, Intent> _shortcutMap;
// Action mapping for a focused slider.
Map<Type, Action<Intent>> _actionMap;
bool get _enabled => widget.onChanged != null;
// Value Indicator Animation that appears on the Overlay. // Value Indicator Animation that appears on the Overlay.
PaintValueIndicator paintValueIndicator; PaintValueIndicator paintValueIndicator;
...@@ -461,6 +482,17 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -461,6 +482,17 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
); );
enableController.value = widget.onChanged != null ? 1.0 : 0.0; enableController.value = widget.onChanged != null ? 1.0 : 0.0;
positionController.value = _unlerp(widget.value); positionController.value = _unlerp(widget.value);
_shortcutMap = <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.arrowUp): const _AdjustSliderIntent.up(),
LogicalKeySet(LogicalKeyboardKey.arrowDown): const _AdjustSliderIntent.down(),
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const _AdjustSliderIntent.left(),
LogicalKeySet(LogicalKeyboardKey.arrowRight): const _AdjustSliderIntent.right(),
};
_actionMap = <Type, Action<Intent>>{
_AdjustSliderIntent: CallbackAction<_AdjustSliderIntent>(
onInvoke: _actionHandler,
),
};
} }
@override @override
...@@ -491,6 +523,53 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -491,6 +523,53 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
widget.onChangeEnd(_lerp(value)); widget.onChangeEnd(_lerp(value));
} }
void _actionHandler (_AdjustSliderIntent intent) {
final _RenderSlider renderSlider = _renderObjectKey.currentContext.findRenderObject() as _RenderSlider;
final TextDirection textDirection = Directionality.of(_renderObjectKey.currentContext);
switch (intent.type) {
case _SliderAdjustmentType.right:
switch (textDirection) {
case TextDirection.rtl:
renderSlider.decreaseAction();
break;
case TextDirection.ltr:
renderSlider.increaseAction();
break;
}
break;
case _SliderAdjustmentType.left:
switch (textDirection) {
case TextDirection.rtl:
renderSlider.increaseAction();
break;
case TextDirection.ltr:
renderSlider.decreaseAction();
break;
}
break;
case _SliderAdjustmentType.up:
renderSlider.increaseAction();
break;
case _SliderAdjustmentType.down:
renderSlider.decreaseAction();
break;
}
}
bool _focused = false;
void _handleFocusHighlightChanged(bool focused) {
if (focused != _focused) {
setState(() { _focused = focused; });
}
}
bool _hovering = false;
void _handleHoverChanged(bool hovering) {
if (hovering != _hovering) {
setState(() { _hovering = hovering; });
}
}
// Returns a number between min and max, proportional to value, which must // Returns a number between min and max, proportional to value, which must
// be between 0.0 and 1.0. // be between 0.0 and 1.0.
double _lerp(double value) { double _lerp(double value) {
...@@ -596,21 +675,33 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -596,21 +675,33 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// in range_slider.dart. // in range_slider.dart.
Size _screenSize() => MediaQuery.of(context).size; Size _screenSize() => MediaQuery.of(context).size;
return CompositedTransformTarget( return FocusableActionDetector(
link: _layerLink, actions: _actionMap,
child: _SliderRenderObjectWidget( shortcuts: _shortcutMap,
value: _unlerp(widget.value), focusNode: widget.focusNode,
divisions: widget.divisions, autofocus: widget.autofocus,
label: widget.label, enabled: _enabled,
sliderTheme: sliderTheme, onShowFocusHighlight: _handleFocusHighlightChanged,
textScaleFactor: MediaQuery.of(context).textScaleFactor, onShowHoverHighlight: _handleHoverChanged,
screenSize: _screenSize(), child: CompositedTransformTarget(
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, link: _layerLink,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null, child: _SliderRenderObjectWidget(
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null, key: _renderObjectKey,
state: this, value: _unlerp(widget.value),
semanticFormatterCallback: widget.semanticFormatterCallback, divisions: widget.divisions,
useV2Slider: widget.useV2Slider, label: widget.label,
sliderTheme: sliderTheme,
textScaleFactor: MediaQuery.of(context).textScaleFactor,
screenSize: _screenSize(),
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this,
semanticFormatterCallback: widget.semanticFormatterCallback,
hasFocus: _focused,
hovering: _hovering,
useV2Slider: widget.useV2Slider,
),
), ),
); );
} }
...@@ -669,6 +760,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -669,6 +760,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
this.onChangeEnd, this.onChangeEnd,
this.state, this.state,
this.semanticFormatterCallback, this.semanticFormatterCallback,
this.hasFocus,
this.hovering,
this.useV2Slider, this.useV2Slider,
}) : super(key: key); }) : super(key: key);
...@@ -683,6 +776,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -683,6 +776,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
final ValueChanged<double> onChangeEnd; final ValueChanged<double> onChangeEnd;
final SemanticFormatterCallback semanticFormatterCallback; final SemanticFormatterCallback semanticFormatterCallback;
final _SliderState state; final _SliderState state;
final bool hasFocus;
final bool hovering;
final bool useV2Slider; final bool useV2Slider;
@override @override
...@@ -701,6 +796,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -701,6 +796,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
semanticFormatterCallback: semanticFormatterCallback, semanticFormatterCallback: semanticFormatterCallback,
platform: Theme.of(context).platform, platform: Theme.of(context).platform,
hasFocus: hasFocus,
hovering: hovering,
useV2Slider: useV2Slider, useV2Slider: useV2Slider,
); );
} }
...@@ -720,7 +817,9 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -720,7 +817,9 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
..onChangeEnd = onChangeEnd ..onChangeEnd = onChangeEnd
..textDirection = Directionality.of(context) ..textDirection = Directionality.of(context)
..semanticFormatterCallback = semanticFormatterCallback ..semanticFormatterCallback = semanticFormatterCallback
..platform = Theme.of(context).platform; ..platform = Theme.of(context).platform
..hasFocus = hasFocus
..hovering = hovering;
// Ticker provider cannot change since there's a 1:1 relationship between // Ticker provider cannot change since there's a 1:1 relationship between
// the _SliderRenderObjectWidget object and the _SliderState object. // the _SliderRenderObjectWidget object and the _SliderState object.
} }
...@@ -741,6 +840,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -741,6 +840,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
this.onChangeEnd, this.onChangeEnd,
@required _SliderState state, @required _SliderState state,
@required TextDirection textDirection, @required TextDirection textDirection,
bool hasFocus,
bool hovering,
bool useV2Slider, bool useV2Slider,
}) : assert(value != null && value >= 0.0 && value <= 1.0), }) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(state != null), assert(state != null),
...@@ -756,6 +857,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -756,6 +857,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_onChanged = onChanged, _onChanged = onChanged,
_state = state, _state = state,
_textDirection = textDirection, _textDirection = textDirection,
_hasFocus = hasFocus,
_hovering = hovering,
_useV2Slider = useV2Slider { _useV2Slider = useV2Slider {
_updateLabelPainter(); _updateLabelPainter();
final GestureArenaTeam team = GestureArenaTeam(); final GestureArenaTeam team = GestureArenaTeam();
...@@ -966,6 +1069,41 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -966,6 +1069,41 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_updateLabelPainter(); _updateLabelPainter();
} }
/// True if this slider has the input focus.
bool get hasFocus => _hasFocus;
bool _hasFocus;
set hasFocus(bool value) {
assert(value != null);
if (value == _hasFocus)
return;
_hasFocus = value;
_updateForFocusOrHover(_hasFocus);
}
/// True if this slider is being hovered over by a pointer.
bool get hovering => _hovering;
bool _hovering;
set hovering(bool value) {
assert(value != null);
if (value == _hovering)
return;
_hovering = value;
_updateForFocusOrHover(_hovering);
}
void _updateForFocusOrHover(bool hasFocusOrIsHovering) {
if (hasFocusOrIsHovering) {
_state.overlayController.forward();
if (showValueIndicator) {
_state.valueIndicatorController.forward();
}
} else {
_state.overlayController.reverse();
if (showValueIndicator) {
_state.valueIndicatorController.reverse();
}
}
}
final bool _useV2Slider; final bool _useV2Slider;
bool get showValueIndicator { bool get showValueIndicator {
...@@ -1297,8 +1435,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1297,8 +1435,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
config.isSemanticBoundary = isInteractive; config.isSemanticBoundary = isInteractive;
if (isInteractive) { if (isInteractive) {
config.textDirection = textDirection; config.textDirection = textDirection;
config.onIncrease = _increaseAction; config.onIncrease = increaseAction;
config.onDecrease = _decreaseAction; config.onDecrease = decreaseAction;
if (semanticFormatterCallback != null) { if (semanticFormatterCallback != null) {
config.value = semanticFormatterCallback(_state._lerp(value)); config.value = semanticFormatterCallback(_state._lerp(value));
config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0) as double)); config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0) as double));
...@@ -1313,19 +1451,42 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1313,19 +1451,42 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _adjustmentUnit; double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _adjustmentUnit;
void _increaseAction() { void increaseAction() {
if (isInteractive) { if (isInteractive) {
onChanged((value + _semanticActionUnit).clamp(0.0, 1.0) as double); onChanged((value + _semanticActionUnit).clamp(0.0, 1.0) as double);
} }
} }
void _decreaseAction() { void decreaseAction() {
if (isInteractive) { if (isInteractive) {
onChanged((value - _semanticActionUnit).clamp(0.0, 1.0) as double); onChanged((value - _semanticActionUnit).clamp(0.0, 1.0) as double);
} }
} }
} }
class _AdjustSliderIntent extends Intent {
const _AdjustSliderIntent({
@required this.type
});
const _AdjustSliderIntent.right() : type = _SliderAdjustmentType.right;
const _AdjustSliderIntent.left() : type = _SliderAdjustmentType.left;
const _AdjustSliderIntent.up() : type = _SliderAdjustmentType.up;
const _AdjustSliderIntent.down() : type = _SliderAdjustmentType.down;
final _SliderAdjustmentType type;
}
enum _SliderAdjustmentType {
right,
left,
up,
down,
}
class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget { class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
const _ValueIndicatorRenderObjectWidget({ const _ValueIndicatorRenderObjectWidget({
this.state, this.state,
......
...@@ -505,7 +505,8 @@ class SliderThemeData with Diagnosticable { ...@@ -505,7 +505,8 @@ class SliderThemeData with Diagnosticable {
/// [Slider] is disabled. /// [Slider] is disabled.
final Color disabledThumbColor; final Color disabledThumbColor;
/// The color of the overlay drawn around the slider thumb when it is pressed. /// The color of the overlay drawn around the slider thumb when it is
/// pressed, focused, or hovered.
/// ///
/// This is typically a semi-transparent color. /// This is typically a semi-transparent color.
final Color overlayColor; final Color overlayColor;
......
...@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart'; ...@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart'; import '../rendering/mock_canvas.dart';
...@@ -652,53 +653,53 @@ void main() { ...@@ -652,53 +653,53 @@ void main() {
await tester.pumpWidget(buildApp()); await tester.pumpWidget(buildApp());
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Check default theme for enabled widget. // Check default theme for enabled widget.
expect(sliderBox, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)); expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..shadow(color: const Color(0xff000000))); expect(material, paints..shadow(color: const Color(0xff000000)));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor)); expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting only the activeColor. // Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1)); await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(sliderBox, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)); expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..shadow(color: Colors.black)); expect(material, paints..shadow(color: Colors.black));
expect(sliderBox, paints..circle(color: customColor1)); expect(material, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
// Test setting only the inactiveColor. // Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1)); await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(sliderBox, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)); expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1));
expect(sliderBox, paints..shadow(color: Colors.black)); expect(material, paints..shadow(color: Colors.black));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor)); expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
// Test setting both activeColor and inactiveColor. // Test setting both activeColor and inactiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2)); await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2));
expect(sliderBox, paints..rrect(color: customColor1)..rrect(color: customColor2)); expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2));
expect(sliderBox, paints..shadow(color: Colors.black)); expect(material, paints..shadow(color: Colors.black));
expect(sliderBox, paints..circle(color: customColor1)); expect(material, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider. // Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3)); await tester.pumpWidget(buildApp(divisions: 3));
expect(sliderBox, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)); expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor));
expect( expect(
sliderBox, material,
paints paints
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
...@@ -707,9 +708,9 @@ void main() { ...@@ -707,9 +708,9 @@ void main() {
..shadow(color: Colors.black) ..shadow(color: Colors.black)
..circle(color: sliderTheme.thumbColor) ..circle(color: sliderTheme.thumbColor)
); );
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set. // Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp( await tester.pumpWidget(buildApp(
...@@ -717,9 +718,9 @@ void main() { ...@@ -717,9 +718,9 @@ void main() {
inactiveColor: customColor2, inactiveColor: customColor2,
divisions: 3, divisions: 3,
)); ));
expect(sliderBox, paints..rrect(color: customColor1)..rrect(color: customColor2)); expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2));
expect( expect(
sliderBox, material,
paints paints
..circle(color: customColor2) ..circle(color: customColor2)
..circle(color: customColor2) ..circle(color: customColor2)
...@@ -727,37 +728,37 @@ void main() { ...@@ -727,37 +728,37 @@ void main() {
..circle(color: customColor1) ..circle(color: customColor1)
..shadow(color: Colors.black) ..shadow(color: Colors.black)
..circle(color: customColor1)); ..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test default theme for disabled widget. // Test default theme for disabled widget.
await tester.pumpWidget(buildApp(enabled: false)); await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
sliderBox, material,
paints paints
..rrect(color: sliderTheme.disabledActiveTrackColor) ..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)); ..rrect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor)); expect(material, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.activeTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
// Test setting the activeColor and inactiveColor for disabled widget. // Test setting the activeColor and inactiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false)); await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
expect( expect(
sliderBox, material,
paints paints
..rrect(color: sliderTheme.disabledActiveTrackColor) ..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)); ..rrect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor)); expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.activeTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
// Test that the default value indicator has the right colors. // Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3)); await tester.pumpWidget(buildApp(divisions: 3));
...@@ -873,58 +874,58 @@ void main() { ...@@ -873,58 +874,58 @@ void main() {
await tester.pumpWidget(buildApp()); await tester.pumpWidget(buildApp());
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Check default theme for enabled widget. // Check default theme for enabled widget.
expect(sliderBox, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor)); expect(material, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor)); expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting only the activeColor. // Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1)); await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveTrackColor)); expect(material, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..circle(color: customColor1)); expect(material, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test setting only the inactiveColor. // Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1)); await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(sliderBox, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: customColor1)); expect(material, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: customColor1));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor)); expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test setting both activeColor and inactiveColor. // Test setting both activeColor and inactiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2)); await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2)); expect(material, paints..rect(color: customColor1)..rect(color: customColor2));
expect(sliderBox, paints..circle(color: customColor1)); expect(material, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider. // Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3)); await tester.pumpWidget(buildApp(divisions: 3));
expect(sliderBox, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor)); expect(material, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor));
expect( expect(
sliderBox, material,
paints paints
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.thumbColor)); ..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set. // Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp( await tester.pumpWidget(buildApp(
...@@ -932,46 +933,46 @@ void main() { ...@@ -932,46 +933,46 @@ void main() {
inactiveColor: customColor2, inactiveColor: customColor2,
divisions: 3, divisions: 3,
)); ));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2)); expect(material, paints..rect(color: customColor1)..rect(color: customColor2));
expect( expect(
sliderBox, material,
paints paints
..circle(color: customColor2) ..circle(color: customColor2)
..circle(color: customColor2) ..circle(color: customColor2)
..circle(color: customColor1) ..circle(color: customColor1)
..circle(color: customColor1) ..circle(color: customColor1)
..circle(color: customColor1)); ..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test default theme for disabled widget. // Test default theme for disabled widget.
await tester.pumpWidget(buildApp(enabled: false)); await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
sliderBox, material,
paints paints
..rect(color: sliderTheme.disabledActiveTrackColor) ..rect(color: sliderTheme.disabledActiveTrackColor)
..rect(color: sliderTheme.disabledInactiveTrackColor)); ..rect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor)); expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.inactiveTrackColor)));
// Test setting the activeColor and inactiveColor for disabled widget. // Test setting the activeColor and inactiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false)); await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
expect( expect(
sliderBox, material,
paints paints
..rect(color: sliderTheme.disabledActiveTrackColor) ..rect(color: sliderTheme.disabledActiveTrackColor)
..rect(color: sliderTheme.disabledInactiveTrackColor)); ..rect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor)); expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveTrackColor))); expect(material, isNot(paints..rect(color: sliderTheme.inactiveTrackColor)));
// Test that the default value indicator has the right colors. // Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3)); await tester.pumpWidget(buildApp(divisions: 3));
...@@ -981,7 +982,7 @@ void main() { ...@@ -981,7 +982,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0)); expect(value, equals(2.0 / 3.0));
expect( expect(
valueIndicatorBox, material,
paints paints
..rect(color: sliderTheme.activeTrackColor) ..rect(color: sliderTheme.activeTrackColor)
..rect(color: sliderTheme.inactiveTrackColor) ..rect(color: sliderTheme.inactiveTrackColor)
...@@ -990,9 +991,9 @@ void main() { ...@@ -990,9 +991,9 @@ void main() {
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.thumbColor) ..circle(color: sliderTheme.thumbColor),
..path(color: sliderTheme.valueIndicatorColor),
); );
expect(valueIndicatorBox, paints..path(color: sliderTheme.valueIndicatorColor));
await gesture.up(); await gesture.up();
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -1009,7 +1010,7 @@ void main() { ...@@ -1009,7 +1010,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0)); expect(value, equals(2.0 / 3.0));
expect( expect(
valueIndicatorBox, material,
paints paints
..rect(color: customColor1) // active track ..rect(color: customColor1) // active track
..rect(color: customColor2) // inactive track ..rect(color: customColor2) // inactive track
...@@ -1018,9 +1019,9 @@ void main() { ...@@ -1018,9 +1019,9 @@ void main() {
..circle(color: customColor2) // 2nd tick mark ..circle(color: customColor2) // 2nd tick mark
..circle(color: customColor2) // 3rd tick mark ..circle(color: customColor2) // 3rd tick mark
..circle(color: customColor1) // 4th tick mark ..circle(color: customColor1) // 4th tick mark
..circle(color: customColor1) // thumb ..circle(color: customColor1) // thumb,
..path(color: customColor1), // indicator
); );
expect(valueIndicatorBox, paints..path(color: customColor1));
await gesture.up(); await gesture.up();
}); });
...@@ -1473,10 +1474,10 @@ void main() { ...@@ -1473,10 +1474,10 @@ void main() {
), ),
); );
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// 5 tick marks and a thumb. // 5 tick marks and a thumb.
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 6)); expect(material, paintsExactlyCountTimes(#drawCircle, 6));
// 200 divisions will produce a tick interval off less than 6, // 200 divisions will produce a tick interval off less than 6,
// which would be too dense to draw. // which would be too dense to draw.
...@@ -1488,7 +1489,7 @@ void main() { ...@@ -1488,7 +1489,7 @@ void main() {
// No tick marks are drawn because they are too dense, but the thumb is // No tick marks are drawn because they are too dense, but the thumb is
// still drawn. // still drawn.
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1)); expect(material, paintsExactlyCountTimes(#drawCircle, 1));
}); });
testWidgets('Slider V2 has correct animations when reparented', (WidgetTester tester) async { testWidgets('Slider V2 has correct animations when reparented', (WidgetTester tester) async {
...@@ -1665,7 +1666,7 @@ void main() { ...@@ -1665,7 +1666,7 @@ void main() {
} }
Future<void> testReparenting(bool reparent) async { Future<void> testReparenting(bool reparent) async {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final Offset center = tester.getCenter(find.byType(Slider)); final Offset center = tester.getCenter(find.byType(Slider));
// Move to 0.0. // Move to 0.0.
TestGesture gesture = await tester.startGesture(Offset.zero); TestGesture gesture = await tester.startGesture(Offset.zero);
...@@ -1674,7 +1675,7 @@ void main() { ...@@ -1674,7 +1675,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 25.0, y: 24.0, radius: 1.0) ..circle(x: 25.0, y: 24.0, radius: 1.0)
..circle(x: 212.5, y: 24.0, radius: 1.0) ..circle(x: 212.5, y: 24.0, radius: 1.0)
...@@ -1690,7 +1691,7 @@ void main() { ...@@ -1690,7 +1691,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 25)); await tester.pump(const Duration(milliseconds: 25));
expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 111.20703125, y: 24.0, radius: 5.687664985656738) ..circle(x: 111.20703125, y: 24.0, radius: 5.687664985656738)
..circle(x: 25.0, y: 24.0, radius: 1.0) ..circle(x: 25.0, y: 24.0, radius: 1.0)
...@@ -1710,7 +1711,7 @@ void main() { ...@@ -1710,7 +1711,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 10)); await tester.pump(const Duration(milliseconds: 10));
expect(SchedulerBinding.instance.transientCallbackCount, equals(2)); expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 190.0135726928711, y: 24.0, radius: 12.0) ..circle(x: 190.0135726928711, y: 24.0, radius: 12.0)
..circle(x: 25.0, y: 24.0, radius: 1.0) ..circle(x: 25.0, y: 24.0, radius: 1.0)
...@@ -1724,7 +1725,7 @@ void main() { ...@@ -1724,7 +1725,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 400.0, y: 24.0, radius: 24.0) ..circle(x: 400.0, y: 24.0, radius: 24.0)
..circle(x: 25.0, y: 24.0, radius: 1.0) ..circle(x: 25.0, y: 24.0, radius: 1.0)
...@@ -1738,7 +1739,7 @@ void main() { ...@@ -1738,7 +1739,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 25.0, y: 24.0, radius: 1.0) ..circle(x: 25.0, y: 24.0, radius: 1.0)
..circle(x: 212.5, y: 24.0, radius: 1.0) ..circle(x: 212.5, y: 24.0, radius: 1.0)
...@@ -1775,14 +1776,16 @@ void main() { ...@@ -1775,14 +1776,16 @@ void main() {
), ),
)); ));
await tester.pumpAndSettle();
expect( expect(
semantics, semantics,
hasSemantics( hasSemantics(
TestSemantics.root(children: <TestSemantics>[ TestSemantics.root(
TestSemantics.rootChild( children: <TestSemantics>[
TestSemantics(
id: 1, id: 1,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 2, id: 2,
...@@ -1790,19 +1793,28 @@ void main() { ...@@ -1790,19 +1793,28 @@ void main() {
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 3, id: 3,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index, flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
value: '50%', children: <TestSemantics>[
increasedValue: '55%', TestSemantics(
decreasedValue: '45%', id: 4,
value: '50%',
increasedValue: '55%',
decreasedValue: '45%',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
), ),
], ],
), ),
], ],
), ),
]), ],
ignoreRect: true, ),
ignoreTransform: true, ignoreRect: true,
)); ignoreTransform: true,
),
);
// Disable slider // Disable slider
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -1821,81 +1833,98 @@ void main() { ...@@ -1821,81 +1833,98 @@ void main() {
)); ));
expect( expect(
semantics, semantics,
hasSemantics( hasSemantics(
TestSemantics.root(children: <TestSemantics>[ TestSemantics.root(
TestSemantics.rootChild( children: <TestSemantics>[
TestSemantics(
id: 1, id: 1,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 2, id: 2,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 5,
),
],
), ),
], ],
), ),
]), ],
ignoreRect: true, ),
ignoreTransform: true, ignoreRect: true,
)); ignoreTransform: true,
),
);
semantics.dispose(); semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgets('Slider Semantics', (WidgetTester tester) async { testWidgets('Slider Semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Theme( home: Theme(
data: ThemeData.light(), data: ThemeData.light(),
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: MediaQuery( child: MediaQuery(
data: MediaQueryData.fromWindow(window), data: MediaQueryData.fromWindow(window),
child: Material( child: Material(
child: Slider( child: Slider(
value: 100.0, value: 100.0,
min: 0.0, min: 0.0,
max: 200.0, max: 200.0,
onChanged: (double v) { }, onChanged: (double v) { },
),
), ),
), ),
), ),
), ),
), )
), );
);
expect(
expect( semantics,
semantics, hasSemantics(
hasSemantics( TestSemantics.root(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 2, id: 1,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 3, id: 2,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index, flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
value: '50%', children: <TestSemantics>[
increasedValue: '60%', TestSemantics(
decreasedValue: '40%', id: 3,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
children: <TestSemantics>[
TestSemantics(
id: 4,
value: '50%',
increasedValue: '60%',
decreasedValue: '40%',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
),
],
), ),
], ],
), ),
], ],
), ),
]), ignoreRect: true,
ignoreRect: true, ignoreTransform: true,
ignoreTransform: true, ),
)); );
semantics.dispose(); semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Slider semantics with custom formatter', (WidgetTester tester) async { testWidgets('Slider semantics with custom formatter', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
...@@ -1920,10 +1949,11 @@ testWidgets('Slider Semantics', (WidgetTester tester) async { ...@@ -1920,10 +1949,11 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
)); ));
expect( expect(
semantics, semantics,
hasSemantics( hasSemantics(
TestSemantics.root(children: <TestSemantics>[ TestSemantics.root(
TestSemantics.rootChild( children: <TestSemantics>[
TestSemantics(
id: 1, id: 1,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <TestSemantics>[ children: <TestSemantics>[
...@@ -1933,22 +1963,308 @@ testWidgets('Slider Semantics', (WidgetTester tester) async { ...@@ -1933,22 +1963,308 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 3, id: 3,
actions: SemanticsAction.increase.index | SemanticsAction.decrease.index, flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
value: '40', children: <TestSemantics>[
increasedValue: '60', TestSemantics(
decreasedValue: '20', id: 4,
value: '40',
increasedValue: '60',
decreasedValue: '20',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
), ),
], ],
), ),
], ],
), ),
]), ],
ignoreRect: true, ),
ignoreTransform: true, ignoreRect: true,
)); ignoreTransform: true,
),
);
semantics.dispose(); semantics.dispose();
}); });
testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return SliderTheme(
data: SliderThemeData(
overlayColor: Colors.orange[500],
),
child: Slider(
value: value,
onChanged: enabled ? (double newValue) {
setState(() {
value = newValue;
});
} : null,
autofocus: true,
focusNode: focusNode,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Check that the overlay shows when focused.
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.orange[500]),
);
// Check that the overlay does not show when focused and disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.orange[500])),
);
});
testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return SliderTheme(
data: SliderThemeData(
overlayColor: Colors.orange[500],
),
child: Slider(
value: value,
onChanged: enabled ? (double newValue) {
setState(() {
value = newValue;
});
} : null,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Slider does not have overlay when enabled and not hovered.
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.orange[500])),
);
// Start hovering.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
// Slider has overlay when enabled and hovering.
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.orange[500]),
);
// Slider does not have an overlay when disabled and hovering.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.orange[500])),
);
});
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.55);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.55);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.6);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.6);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - RTL', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.rtl,
child: Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
),
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.45);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.55);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - RTL', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.rtl,
child: Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
),
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.4);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.6);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Value indicator appears when it should', (WidgetTester tester) async { testWidgets('Value indicator appears when it should', (WidgetTester tester) async {
final ThemeData baseTheme = ThemeData( final ThemeData baseTheme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
...@@ -1997,6 +2313,7 @@ testWidgets('Slider Semantics', (WidgetTester tester) async { ...@@ -1997,6 +2313,7 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
// Wait for value indicator animation to finish. // Wait for value indicator animation to finish.
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
expect( expect(
valueIndicatorBox, valueIndicatorBox,
......
...@@ -120,10 +120,10 @@ void main() { ...@@ -120,10 +120,10 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme; final SliderThemeData sliderTheme = theme.sliderTheme;
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints paints
..rect(color: sliderTheme.disabledActiveTrackColor) ..rect(color: sliderTheme.disabledActiveTrackColor)
..rect(color: sliderTheme.disabledInactiveTrackColor), ..rect(color: sliderTheme.disabledInactiveTrackColor),
...@@ -142,10 +142,10 @@ void main() { ...@@ -142,10 +142,10 @@ void main() {
); );
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false, useV2Slider: true)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints paints
..rrect(color: customTheme.disabledActiveTrackColor) ..rrect(color: customTheme.disabledActiveTrackColor)
..rrect(color: customTheme.disabledInactiveTrackColor), ..rrect(color: customTheme.disabledInactiveTrackColor),
...@@ -164,10 +164,10 @@ void main() { ...@@ -164,10 +164,10 @@ void main() {
); );
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints paints
..rect(color: customTheme.disabledActiveTrackColor) ..rect(color: customTheme.disabledActiveTrackColor)
..rect(color: customTheme.disabledInactiveTrackColor), ..rect(color: customTheme.disabledInactiveTrackColor),
...@@ -266,7 +266,7 @@ void main() { ...@@ -266,7 +266,7 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
const Radius radius = Radius.circular(2); const Radius radius = Radius.circular(2);
const Radius activatedRadius = Radius.circular(3); const Radius activatedRadius = Radius.circular(3);
...@@ -274,7 +274,7 @@ void main() { ...@@ -274,7 +274,7 @@ void main() {
// The enabled slider thumb has track segments that extend to and from // The enabled slider thumb has track segments that extend to and from
// the center of the thumb. // the center of the thumb.
expect( expect(
sliderBox, material,
paints paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor) ..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor), ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
...@@ -285,7 +285,7 @@ void main() { ...@@ -285,7 +285,7 @@ void main() {
// The disabled slider thumb is the same size as the enabled thumb. // The disabled slider thumb is the same size as the enabled thumb.
expect( expect(
sliderBox, material,
paints paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor) ..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor), ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
...@@ -300,12 +300,12 @@ void main() { ...@@ -300,12 +300,12 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// The enabled slider thumb has track segments that extend to and from // The enabled slider thumb has track segments that extend to and from
// the center of the thumb. // the center of the thumb.
expect( expect(
sliderBox, material,
paints paints
..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.activeTrackColor) ..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.activeTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.inactiveTrackColor), ..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.inactiveTrackColor),
...@@ -316,7 +316,7 @@ void main() { ...@@ -316,7 +316,7 @@ void main() {
// The disabled slider thumb is the same size as the enabled thumb. // The disabled slider thumb is the same size as the enabled thumb.
expect( expect(
sliderBox, material,
paints paints
..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.disabledActiveTrackColor) ..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.disabledActiveTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.disabledInactiveTrackColor), ..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.disabledInactiveTrackColor),
...@@ -331,11 +331,11 @@ void main() { ...@@ -331,11 +331,11 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// With no touch, paints only the thumb. // With no touch, paints only the thumb.
expect( expect(
sliderBox, material,
paints paints
..circle( ..circle(
color: sliderTheme.thumbColor, color: sliderTheme.thumbColor,
...@@ -352,7 +352,7 @@ void main() { ...@@ -352,7 +352,7 @@ void main() {
// After touch, paints thumb and overlay. // After touch, paints thumb and overlay.
expect( expect(
sliderBox, material,
paints paints
..circle( ..circle(
color: sliderTheme.overlayColor, color: sliderTheme.overlayColor,
...@@ -373,7 +373,7 @@ void main() { ...@@ -373,7 +373,7 @@ void main() {
// After the gesture is up and complete, it again paints only the thumb. // After the gesture is up and complete, it again paints only the thumb.
expect( expect(
sliderBox, material,
paints paints
..circle( ..circle(
color: sliderTheme.thumbColor, color: sliderTheme.thumbColor,
...@@ -392,20 +392,20 @@ void main() { ...@@ -392,20 +392,20 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500); final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor, radius: 10.0)); expect(material, paints..circle(color: sliderTheme.thumbColor, radius: 10.0));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, enabled: false)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation await tester.pumpAndSettle(); // wait for disable animation
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0)); expect(material, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3));
await tester.pumpAndSettle(); // wait for enable animation await tester.pumpAndSettle(); // wait for enable animation
expect( expect(
sliderBox, material,
paints paints
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor)
...@@ -418,7 +418,7 @@ void main() { ...@@ -418,7 +418,7 @@ void main() {
await tester.pumpAndSettle(); // wait for disable animation await tester.pumpAndSettle(); // wait for disable animation
expect( expect(
sliderBox, material,
paints paints
..circle(color: sliderTheme.disabledActiveTickMarkColor) ..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor) ..circle(color: sliderTheme.disabledInactiveTickMarkColor)
...@@ -791,11 +791,11 @@ void main() { ...@@ -791,11 +791,11 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Top and bottom are centerY (300) + and - trackRadius (8). // Top and bottom are centerY (300) + and - trackRadius (8).
expect( expect(
sliderBox, material,
paints paints
..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.activeTrackColor) ..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.activeTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.inactiveTrackColor), ..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.inactiveTrackColor),
...@@ -807,7 +807,7 @@ void main() { ...@@ -807,7 +807,7 @@ void main() {
// The disabled thumb is smaller so the active track has to paint longer to // The disabled thumb is smaller so the active track has to paint longer to
// get to the edge. // get to the edge.
expect( expect(
sliderBox, material,
paints paints
..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.disabledActiveTrackColor) ..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.disabledActiveTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.disabledInactiveTrackColor), ..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.disabledInactiveTrackColor),
...@@ -821,11 +821,11 @@ void main() { ...@@ -821,11 +821,11 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Top and bottom are centerY (300) + and - trackRadius (8). // Top and bottom are centerY (300) + and - trackRadius (8).
expect( expect(
sliderBox, material,
paints paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor) ..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor), ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
...@@ -837,7 +837,7 @@ void main() { ...@@ -837,7 +837,7 @@ void main() {
// The disabled thumb is smaller so the active track has to paint longer to // The disabled thumb is smaller so the active track has to paint longer to
// get to the edge. // get to the edge.
expect( expect(
sliderBox, material,
paints paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor) ..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor), ..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
...@@ -853,10 +853,10 @@ void main() { ...@@ -853,10 +853,10 @@ void main() {
); );
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints..circle(x: 212, y: 300, radius: 7, color: sliderTheme.thumbColor), paints..circle(x: 212, y: 300, radius: 7, color: sliderTheme.thumbColor),
); );
...@@ -864,7 +864,7 @@ void main() { ...@@ -864,7 +864,7 @@ void main() {
await tester.pumpAndSettle(); // wait for disable animation await tester.pumpAndSettle(); // wait for disable animation
expect( expect(
sliderBox, material,
paints..circle(x: 212, y: 300, radius: 11, color: sliderTheme.disabledThumbColor), paints..circle(x: 212, y: 300, radius: 11, color: sliderTheme.disabledThumbColor),
); );
}); });
...@@ -877,17 +877,17 @@ void main() { ...@@ -877,17 +877,17 @@ void main() {
); );
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.thumbColor), paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.thumbColor),
); );
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation await tester.pumpAndSettle(); // wait for disable animation
expect( expect(
sliderBox, material,
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.disabledThumbColor), paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.disabledThumbColor),
); );
}); });
...@@ -903,10 +903,10 @@ void main() { ...@@ -903,10 +903,10 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 29, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor) ..circle(x: 29, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor) ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
...@@ -917,7 +917,7 @@ void main() { ...@@ -917,7 +917,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
sliderBox, material,
paints paints
..circle(x: 29, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor) ..circle(x: 29, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor) ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
...@@ -936,10 +936,10 @@ void main() { ...@@ -936,10 +936,10 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, useV2Slider: true)); await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor) ..circle(x: 26, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor) ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
...@@ -950,7 +950,7 @@ void main() { ...@@ -950,7 +950,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
sliderBox, material,
paints paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor) ..circle(x: 26, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor) ..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
...@@ -972,9 +972,9 @@ void main() { ...@@ -972,9 +972,9 @@ void main() {
await tester.startGesture(center); await tester.startGesture(center);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect( expect(
sliderBox, material,
paints..circle( paints..circle(
x: center.dx, x: center.dx,
y: center.dy, y: center.dy,
...@@ -1005,9 +1005,11 @@ void main() { ...@@ -1005,9 +1005,11 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(sliderBox, paintsNothing); expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
}); });
testWidgets('The slider can skip all component painting except the track', (WidgetTester tester) async { testWidgets('The slider can skip all component painting except the track', (WidgetTester tester) async {
...@@ -1023,12 +1025,12 @@ void main() { ...@@ -1023,12 +1025,12 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 2 track segments. // Only 2 track segments.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 2)); expect(material, paintsExactlyCountTimes(#drawRect, 2));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 0)); expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0)); expect(material, paintsExactlyCountTimes(#drawPath, 0));
}); });
testWidgets('The slider can skip all component painting except the tick marks', (WidgetTester tester) async { testWidgets('The slider can skip all component painting except the tick marks', (WidgetTester tester) async {
...@@ -1047,12 +1049,12 @@ void main() { ...@@ -1047,12 +1049,12 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 5 tick marks. // Only 5 tick marks.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0)); expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 5)); expect(material, paintsExactlyCountTimes(#drawCircle, 5));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0)); expect(material, paintsExactlyCountTimes(#drawPath, 0));
}); });
testWidgets('The slider can skip all component painting except the thumb', (WidgetTester tester) async { testWidgets('The slider can skip all component painting except the thumb', (WidgetTester tester) async {
...@@ -1068,12 +1070,12 @@ void main() { ...@@ -1068,12 +1070,12 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 1 thumb. // Only 1 thumb.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0)); expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1)); expect(material, paintsExactlyCountTimes(#drawCircle, 1));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0)); expect(material, paintsExactlyCountTimes(#drawPath, 0));
}); });
testWidgets('The slider can skip all component painting except the overlay', (WidgetTester tester) async { testWidgets('The slider can skip all component painting except the overlay', (WidgetTester tester) async {
...@@ -1089,7 +1091,7 @@ void main() { ...@@ -1089,7 +1091,7 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider)); final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Tap the center of the track and wait for animations to finish. // Tap the center of the track and wait for animations to finish.
final Offset center = tester.getCenter(find.byType(Slider)); final Offset center = tester.getCenter(find.byType(Slider));
...@@ -1097,9 +1099,9 @@ void main() { ...@@ -1097,9 +1099,9 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Only 1 overlay. // Only 1 overlay.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0)); expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1)); expect(material, paintsExactlyCountTimes(#drawCircle, 1));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0)); expect(material, paintsExactlyCountTimes(#drawPath, 0));
await gesture.up(); await gesture.up();
}); });
...@@ -1118,6 +1120,7 @@ void main() { ...@@ -1118,6 +1120,7 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track and wait for animations to finish. // Tap the center of the track and wait for animations to finish.
...@@ -1126,8 +1129,8 @@ void main() { ...@@ -1126,8 +1129,8 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Only 1 value indicator. // Only 1 value indicator.
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawRect, 0)); expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawCircle, 0)); expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1)); expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up(); await gesture.up();
...@@ -1148,6 +1151,7 @@ void main() { ...@@ -1148,6 +1151,7 @@ void main() {
divisions: 4, divisions: 4,
)); ));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay)); final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator. // Tap the center of the track to kick off the animation of the value indicator.
...@@ -1156,6 +1160,8 @@ void main() { ...@@ -1156,6 +1160,8 @@ void main() {
// Nothing to paint at scale 0. // Nothing to paint at scale 0.
await tester.pump(); await tester.pump();
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0)); expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for the value indicator. // Painting a path for the value indicator.
......
...@@ -194,11 +194,9 @@ void main() { ...@@ -194,11 +194,9 @@ void main() {
SystemChannels.system.codec.encodeMessage(data), SystemChannels.system.codec.encodeMessage(data),
(ByteData data) { }, (ByteData data) { },
); );
final RenderObject renderObject = tester.renderObject(find.byType(Slider)); // _RenderSlider is the last render object in the tree.
final RenderObject renderObject = tester.allRenderObjects.last;
bool sliderBoxNeedsLayout; expect(renderObject.debugNeedsLayout, isTrue);
renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;});
expect(sliderBoxNeedsLayout, isTrue);
}); });
testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async { testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async {
......
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