Unverified Commit 97d0247d authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Add Material 3 support for `Slider` - Part 1 (#114079)

parent f10021b3
...@@ -38,6 +38,7 @@ import 'package:gen_defaults/navigation_rail_template.dart'; ...@@ -38,6 +38,7 @@ import 'package:gen_defaults/navigation_rail_template.dart';
import 'package:gen_defaults/popup_menu_template.dart'; import 'package:gen_defaults/popup_menu_template.dart';
import 'package:gen_defaults/progress_indicator_template.dart'; import 'package:gen_defaults/progress_indicator_template.dart';
import 'package:gen_defaults/radio_template.dart'; import 'package:gen_defaults/radio_template.dart';
import 'package:gen_defaults/slider_template.dart';
import 'package:gen_defaults/surface_tint.dart'; import 'package:gen_defaults/surface_tint.dart';
import 'package:gen_defaults/switch_template.dart'; import 'package:gen_defaults/switch_template.dart';
import 'package:gen_defaults/text_field_template.dart'; import 'package:gen_defaults/text_field_template.dart';
...@@ -144,6 +145,7 @@ Future<void> main(List<String> args) async { ...@@ -144,6 +145,7 @@ Future<void> main(List<String> args) async {
PopupMenuTemplate('PopupMenu', '$materialLib/popup_menu.dart', tokens).updateFile(); PopupMenuTemplate('PopupMenu', '$materialLib/popup_menu.dart', tokens).updateFile();
ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile(); ProgressIndicatorTemplate('ProgressIndicator', '$materialLib/progress_indicator.dart', tokens).updateFile();
RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile(); RadioTemplate('Radio<T>', '$materialLib/radio.dart', tokens).updateFile();
SliderTemplate('md.comp.slider', 'Slider', '$materialLib/slider.dart', tokens).updateFile();
SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile(); SurfaceTintTemplate('SurfaceTint', '$materialLib/elevation_overlay.dart', tokens).updateFile();
SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile(); SwitchTemplate('Switch', '$materialLib/switch.dart', tokens).updateFile();
TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile(); TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile();
......
// 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 'template.dart';
class SliderTemplate extends TokenTemplate {
const SliderTemplate(this.tokenGroup, super.blockName, super.fileName, super.tokens, {
super.colorSchemePrefix = '_colors.',
});
final String tokenGroup;
@override
String generate() => '''
class _${blockName}DefaultsM3 extends SliderThemeData {
_${blockName}DefaultsM3(this.context)
: _colors = Theme.of(context).colorScheme,
super(trackHeight: ${tokens['$tokenGroup.active.track.height']});
final BuildContext context;
final ColorScheme _colors;
@override
Color? get activeTrackColor => ${componentColor('$tokenGroup.active.track')};
@override
Color? get inactiveTrackColor => ${componentColor('$tokenGroup.inactive.track')};
@override
Color? get secondaryActiveTrackColor => _colors.primary.withOpacity(0.54);
@override
Color? get disabledActiveTrackColor => ${componentColor('$tokenGroup.disabled.active.track')};
@override
Color? get disabledInactiveTrackColor => ${componentColor('$tokenGroup.disabled.inactive.track')};
@override
Color? get disabledSecondaryActiveTrackColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get activeTickMarkColor => ${componentColor('$tokenGroup.with-tick-marks.active.container')};
@override
Color? get inactiveTickMarkColor => ${componentColor('$tokenGroup.with-tick-marks.inactive.container')};
@override
Color? get disabledActiveTickMarkColor => ${componentColor('$tokenGroup.with-tick-marks.disabled.container')};
@override
Color? get disabledInactiveTickMarkColor => ${componentColor('$tokenGroup.with-tick-marks.disabled.container')};
@override
Color? get thumbColor => ${componentColor('$tokenGroup.handle')};
@override
Color? get disabledThumbColor => Color.alphaBlend(${componentColor('$tokenGroup.disabled.handle')}, _colors.surface);
@override
Color? get overlayColor => MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return ${componentColor('$tokenGroup.hover.state-layer')};
}
if (states.contains(MaterialState.focused)) {
return ${componentColor('$tokenGroup.focus.state-layer')};
}
if (states.contains(MaterialState.dragged)) {
return ${componentColor('$tokenGroup.pressed.state-layer')};
}
return Colors.transparent;
});
}
''';
}
...@@ -12,6 +12,8 @@ import 'package:flutter/rendering.dart'; ...@@ -12,6 +12,8 @@ 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/services.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart'; import 'constants.dart';
import 'debug.dart'; import 'debug.dart';
import 'material.dart'; import 'material.dart';
...@@ -374,8 +376,9 @@ class Slider extends StatefulWidget { ...@@ -374,8 +376,9 @@ class Slider extends StatefulWidget {
/// The "active" side of the slider is the side between the thumb and the /// The "active" side of the slider is the side between the thumb and the
/// minimum value. /// minimum value.
/// ///
/// Defaults to [SliderThemeData.activeTrackColor] of the current /// If null, [SliderThemeData.activeTrackColor] of the ambient
/// [SliderTheme]. /// [SliderTheme] is used. If that is null, [ColorScheme.primary] of the
/// surrounding [ThemeData] is used.
/// ///
/// Using a [SliderTheme] gives much more fine-grained control over the /// Using a [SliderTheme] gives much more fine-grained control over the
/// appearance of various components of the slider. /// appearance of various components of the slider.
...@@ -386,8 +389,10 @@ class Slider extends StatefulWidget { ...@@ -386,8 +389,10 @@ class Slider extends StatefulWidget {
/// The "inactive" side of the slider is the side between the thumb and the /// The "inactive" side of the slider is the side between the thumb and the
/// maximum value. /// maximum value.
/// ///
/// Defaults to the [SliderThemeData.inactiveTrackColor] of the current /// If null, [SliderThemeData.inactiveTrackColor] of the ambient [SliderTheme]
/// [SliderTheme]. /// is used. If that is null and [ThemeData.useMaterial3] is true,
/// [ColorScheme.surfaceVariant] will be used, otherwise [ColorScheme.primary]
/// with an opacity of 0.24 will be used.
/// ///
/// Using a [SliderTheme] gives much more fine-grained control over the /// Using a [SliderTheme] gives much more fine-grained control over the
/// appearance of various components of the slider. /// appearance of various components of the slider.
...@@ -401,6 +406,9 @@ class Slider extends StatefulWidget { ...@@ -401,6 +406,9 @@ class Slider extends StatefulWidget {
/// Defaults to the [SliderThemeData.secondaryActiveTrackColor] of the current /// Defaults to the [SliderThemeData.secondaryActiveTrackColor] of the current
/// [SliderTheme]. /// [SliderTheme].
/// ///
/// If that is also null, defaults to [ColorScheme.primary] with an
/// opacity of 0.54.
///
/// Using a [SliderTheme] gives much more fine-grained control over the /// Using a [SliderTheme] gives much more fine-grained control over the
/// appearance of various components of the slider. /// appearance of various components of the slider.
/// ///
...@@ -409,19 +417,27 @@ class Slider extends StatefulWidget { ...@@ -409,19 +417,27 @@ class Slider extends StatefulWidget {
/// The color of the thumb. /// The color of the thumb.
/// ///
/// If this color is null: /// If this color is null, [Slider] will use [activeColor], If [activeColor]
/// * [Slider] will use [activeColor]. /// is also null, [Slider] will use [SliderThemeData.thumbColor].
///
/// If that is also null, defaults to [ColorScheme.primary].
///
/// * [CupertinoSlider] will have a white thumb /// * [CupertinoSlider] will have a white thumb
/// (like the native default iOS slider). /// (like the native default iOS slider).
final Color? thumbColor; final Color? thumbColor;
/// The highlight color that's typically used to indicate that /// The highlight color that's typically used to indicate that
/// the slider is focused, hovered, or dragged. /// the slider thumb is focused, hovered, or dragged.
/// ///
/// If this property is null, [Slider] will use [activeColor] with /// If this property is null, [Slider] will use [activeColor] with
/// with an opacity of 0.12, If null, [SliderThemeData.overlayColor] /// with an opacity of 0.12, If null, [SliderThemeData.overlayColor]
/// will be used, If this is also null, defaults to [ColorScheme.primary] /// will be used.
/// with an opacity of 0.12. ///
/// If that is also null, If [ThemeData.useMaterial3] is true,
/// Slider will use [ColorScheme.primary] with an opacity of 0.08 when
/// slider thumb is hovered and with an opacity of 0.12 when slider thumb
/// is focused or dragged, If [ThemeData.useMaterial3] is false, defaults
/// to [ColorScheme.primary] with an opacity of 0.12.
final MaterialStateProperty<Color?>? overlayColor; final MaterialStateProperty<Color?>? overlayColor;
/// {@template flutter.material.slider.mouseCursor} /// {@template flutter.material.slider.mouseCursor}
...@@ -730,6 +746,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -730,6 +746,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
Widget _buildMaterialSlider(BuildContext context) { Widget _buildMaterialSlider(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
SliderThemeData sliderTheme = SliderTheme.of(context); SliderThemeData sliderTheme = SliderTheme.of(context);
final SliderThemeData defaults = theme.useMaterial3 ? _SliderDefaultsM3(context) : _SliderDefaultsM2(context);
// If the widget has active or inactive colors specified, then we plug them // If the widget has active or inactive colors specified, then we plug them
// in to the slider theme as best we can. If the developer wants more // in to the slider theme as best we can. If the developer wants more
...@@ -738,7 +755,6 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -738,7 +755,6 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// the default shapes and text styles are aligned to the Material // the default shapes and text styles are aligned to the Material
// Guidelines. // Guidelines.
const double defaultTrackHeight = 4;
const SliderTrackShape defaultTrackShape = RoundedRectSliderTrackShape(); const SliderTrackShape defaultTrackShape = RoundedRectSliderTrackShape();
const SliderTickMarkShape defaultTickMarkShape = RoundSliderTickMarkShape(); const SliderTickMarkShape defaultTickMarkShape = RoundSliderTickMarkShape();
const SliderComponentShape defaultOverlayShape = RoundSliderOverlayShape(); const SliderComponentShape defaultOverlayShape = RoundSliderOverlayShape();
...@@ -769,23 +785,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -769,23 +785,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
return widget.overlayColor?.resolve(states) return widget.overlayColor?.resolve(states)
?? widget.activeColor?.withOpacity(0.12) ?? widget.activeColor?.withOpacity(0.12)
?? MaterialStateProperty.resolveAs<Color?>(sliderTheme.overlayColor, states) ?? MaterialStateProperty.resolveAs<Color?>(sliderTheme.overlayColor, states)
?? theme.colorScheme.primary.withOpacity(0.12); ?? MaterialStateProperty.resolveAs<Color?>(defaults.overlayColor, states);
} }
sliderTheme = sliderTheme.copyWith( sliderTheme = sliderTheme.copyWith(
trackHeight: sliderTheme.trackHeight ?? defaultTrackHeight, trackHeight: sliderTheme.trackHeight ?? defaults.trackHeight,
activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary, activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? defaults.activeTrackColor,
inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.24), inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? defaults.inactiveTrackColor,
secondaryActiveTrackColor: widget.secondaryActiveColor ?? sliderTheme.secondaryActiveTrackColor ?? theme.colorScheme.primary.withOpacity(0.54), secondaryActiveTrackColor: widget.secondaryActiveColor ?? sliderTheme.secondaryActiveTrackColor ?? defaults.secondaryActiveTrackColor,
disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.32), disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? defaults.disabledActiveTrackColor,
disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12), disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? defaults.disabledInactiveTrackColor,
disabledSecondaryActiveTrackColor: sliderTheme.disabledSecondaryActiveTrackColor ?? theme.colorScheme.onSurface.withOpacity(0.12), disabledSecondaryActiveTrackColor: sliderTheme.disabledSecondaryActiveTrackColor ?? defaults.disabledSecondaryActiveTrackColor,
activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.54), activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? defaults.activeTickMarkColor,
inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? theme.colorScheme.primary.withOpacity(0.54), inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? defaults.inactiveTickMarkColor,
disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? theme.colorScheme.onPrimary.withOpacity(0.12), disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? defaults.disabledActiveTickMarkColor,
disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12), disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? defaults.disabledInactiveTickMarkColor,
thumbColor: widget.thumbColor ?? widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary, thumbColor: widget.thumbColor ?? widget.activeColor ?? sliderTheme.thumbColor ?? defaults.thumbColor,
disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), theme.colorScheme.surface), disabledThumbColor: sliderTheme.disabledThumbColor ?? defaults.disabledThumbColor,
overlayColor: effectiveOverlayColor(), overlayColor: effectiveOverlayColor(),
valueIndicatorColor: valueIndicatorColor, valueIndicatorColor: valueIndicatorColor,
trackShape: sliderTheme.trackShape ?? defaultTrackShape, trackShape: sliderTheme.trackShape ?? defaultTrackShape,
...@@ -1795,3 +1811,122 @@ class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChange ...@@ -1795,3 +1811,122 @@ class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChange
return constraints.smallest; return constraints.smallest;
} }
} }
class _SliderDefaultsM2 extends SliderThemeData {
_SliderDefaultsM2(this.context)
: _colors = Theme.of(context).colorScheme,
super(trackHeight: 4.0);
final BuildContext context;
final ColorScheme _colors;
@override
Color? get activeTrackColor => _colors.primary;
@override
Color? get inactiveTrackColor => _colors.primary.withOpacity(0.24);
@override
Color? get secondaryActiveTrackColor => _colors.primary.withOpacity(0.54);
@override
Color? get disabledActiveTrackColor => _colors.onSurface.withOpacity(0.32);
@override
Color? get disabledInactiveTrackColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get disabledSecondaryActiveTrackColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get activeTickMarkColor => _colors.onPrimary.withOpacity(0.54);
@override
Color? get inactiveTickMarkColor => _colors.primary.withOpacity(0.54);
@override
Color? get disabledActiveTickMarkColor => _colors.onPrimary.withOpacity(0.12);
@override
Color? get disabledInactiveTickMarkColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get thumbColor => _colors.primary;
@override
Color? get disabledThumbColor => Color.alphaBlend(_colors.onSurface.withOpacity(.38), _colors.surface);
@override
Color? get overlayColor => _colors.primary.withOpacity(0.12);
}
// BEGIN GENERATED TOKEN PROPERTIES - Slider
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// Token database version: v0_137
class _SliderDefaultsM3 extends SliderThemeData {
_SliderDefaultsM3(this.context)
: _colors = Theme.of(context).colorScheme,
super(trackHeight: 4.0);
final BuildContext context;
final ColorScheme _colors;
@override
Color? get activeTrackColor => _colors.primary;
@override
Color? get inactiveTrackColor => _colors.surfaceVariant;
@override
Color? get secondaryActiveTrackColor => _colors.primary.withOpacity(0.54);
@override
Color? get disabledActiveTrackColor => _colors.onSurface.withOpacity(0.38);
@override
Color? get disabledInactiveTrackColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get disabledSecondaryActiveTrackColor => _colors.onSurface.withOpacity(0.12);
@override
Color? get activeTickMarkColor => _colors.onPrimary.withOpacity(0.38);
@override
Color? get inactiveTickMarkColor => _colors.onSurfaceVariant.withOpacity(0.38);
@override
Color? get disabledActiveTickMarkColor => _colors.onSurface.withOpacity(0.38);
@override
Color? get disabledInactiveTickMarkColor => _colors.onSurface.withOpacity(0.38);
@override
Color? get thumbColor => _colors.primary;
@override
Color? get disabledThumbColor => Color.alphaBlend(_colors.onSurface.withOpacity(0.38), _colors.surface);
@override
Color? get overlayColor => MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return _colors.primary.withOpacity(0.08);
}
if (states.contains(MaterialState.focused)) {
return _colors.primary.withOpacity(0.12);
}
if (states.contains(MaterialState.dragged)) {
return _colors.primary.withOpacity(0.12);
}
return Colors.transparent;
});
}
// END GENERATED TOKEN PROPERTIES - Slider
...@@ -1710,28 +1710,25 @@ void main() { ...@@ -1710,28 +1710,25 @@ void main() {
testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async { testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Slider'); final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final ThemeData theme = ThemeData(useMaterial3: true);
double value = 0.5; double value = 0.5;
Widget buildApp({bool enabled = true}) { Widget buildApp({bool enabled = true}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return SliderTheme( return Slider(
data: SliderThemeData( value: value,
overlayColor: Colors.orange[500], onChanged: enabled
), ? (double newValue) {
child: Slider( setState(() {
value: value, value = newValue;
onChanged: enabled });
? (double newValue) { }
setState(() { : null,
value = newValue; autofocus: true,
}); focusNode: focusNode,
}
: null,
autofocus: true,
focusNode: focusNode,
),
); );
}), }),
), ),
...@@ -1745,7 +1742,7 @@ void main() { ...@@ -1745,7 +1742,7 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue); expect(focusNode.hasPrimaryFocus, isTrue);
expect( expect(
Material.of(tester.element(find.byType(Slider))), Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.orange[500]), paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
); );
// Check that the overlay does not show when unfocused and disabled. // Check that the overlay does not show when unfocused and disabled.
...@@ -1754,7 +1751,7 @@ void main() { ...@@ -1754,7 +1751,7 @@ void main() {
expect(focusNode.hasPrimaryFocus, isFalse); expect(focusNode.hasPrimaryFocus, isFalse);
expect( expect(
Material.of(tester.element(find.byType(Slider))), Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.orange[500])), isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
); );
}); });
...@@ -1813,26 +1810,23 @@ void main() { ...@@ -1813,26 +1810,23 @@ void main() {
testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async { testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final ThemeData theme = ThemeData(useMaterial3: true);
double value = 0.5; double value = 0.5;
Widget buildApp({bool enabled = true}) { Widget buildApp({bool enabled = true}) {
return MaterialApp( return MaterialApp(
theme: theme,
home: Material( home: Material(
child: Center( child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return SliderTheme( return Slider(
data: SliderThemeData( value: value,
overlayColor: Colors.orange[500], onChanged: enabled
), ? (double newValue) {
child: Slider( setState(() {
value: value, value = newValue;
onChanged: enabled });
? (double newValue) { }
setState(() { : null,
value = newValue;
});
}
: null,
),
); );
}), }),
), ),
...@@ -1858,7 +1852,7 @@ void main() { ...@@ -1858,7 +1852,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
Material.of(tester.element(find.byType(Slider))), Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.orange[500]), paints..circle(color: theme.colorScheme.primary.withOpacity(0.08)),
); );
// Slider does not have an overlay when disabled and hovered. // Slider does not have an overlay when disabled and hovered.
...@@ -1931,6 +1925,67 @@ void main() { ...@@ -1931,6 +1925,67 @@ void main() {
); );
}); });
testWidgets('Slider is draggable and has correct dragged color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
final ThemeData theme = ThemeData(useMaterial3: true);
final Key sliderKey = UniqueKey();
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
key: sliderKey,
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Slider does not have overlay when enabled and not dragged.
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
);
// Start dragging.
final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
await tester.pump(kPressTimeout);
// Less than configured touch slop, more than default touch slop
await drag.moveBy(const Offset(19.0, 0));
await tester.pump();
// Slider has overlay when enabled and dragged.
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
);
await drag.up();
await tester.pumpAndSettle();
// Slider still has overlay when stopped dragging.
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
);
});
testWidgets('Slider has correct dragged color from overlayColor property', (WidgetTester tester) async { testWidgets('Slider has correct dragged color from overlayColor property', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5; double value = 0.5;
...@@ -3381,4 +3436,171 @@ void main() { ...@@ -3381,4 +3436,171 @@ void main() {
isNot(paints..path(color: const Color(0xff000000))..paragraph()), isNot(paints..path(color: const Color(0xff000000))..paragraph()),
); );
}, variant: TargetPlatformVariant.desktop()); }, variant: TargetPlatformVariant.desktop());
group('Material 2', () {
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
// is turned on by default, these tests can be removed.
testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final ThemeData theme = ThemeData();
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return 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: theme.colorScheme.primary.withOpacity(0.12))),
);
// Start hovering.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
// Slider has overlay when enabled and hovered.
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
);
// Slider does not have an overlay when disabled and hovered.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
);
});
testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final ThemeData theme = ThemeData();
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return 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: theme.colorScheme.primary.withOpacity(0.12)),
);
// Check that the overlay does not show when unfocused 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: theme.colorScheme.primary.withOpacity(0.12))),
);
});
testWidgets('Slider is draggable and has correct dragged color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
final ThemeData theme = ThemeData();
final Key sliderKey = UniqueKey();
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
key: sliderKey,
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Slider does not have overlay when enabled and not dragged.
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))),
);
// Start dragging.
final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
await tester.pump(kPressTimeout);
// Less than configured touch slop, more than default touch slop
await drag.moveBy(const Offset(19.0, 0));
await tester.pump();
// Slider has overlay when enabled and dragged.
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
);
await drag.up();
await tester.pumpAndSettle();
// Slider still has overlay when stopped dragging.
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)),
);
});
});
} }
...@@ -97,6 +97,137 @@ void main() { ...@@ -97,6 +97,137 @@ void main() {
]); ]);
}); });
testWidgets('Slider defaults', (WidgetTester tester) async {
debugDisableShadows = false;
final ThemeData theme = ThemeData(useMaterial3: true);
final ColorScheme colorScheme = theme.colorScheme;
const double trackHeight = 4.0;
final Color activeTrackColor = Color(colorScheme.primary.value);
final Color inactiveTrackColor = colorScheme.surfaceVariant;
final Color secondaryActiveTrackColor = colorScheme.primary.withOpacity(0.54);
final Color disabledActiveTrackColor = colorScheme.onSurface.withOpacity(0.38);
final Color disabledInactiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color disabledSecondaryActiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color shadowColor = colorScheme.shadow;
final Color thumbColor = Color(colorScheme.primary.value);
final Color disabledThumbColor = Color.alphaBlend(colorScheme.onSurface.withOpacity(0.38), colorScheme.surface);
final Color activeTickMarkColor = colorScheme.onPrimary.withOpacity(0.38);
final Color inactiveTickMarkColor = colorScheme.onSurfaceVariant.withOpacity(0.38);
final Color disabledActiveTickMarkColor = colorScheme.onSurface.withOpacity(0.38);
final Color disabledInactiveTickMarkColor = colorScheme.onSurface.withOpacity(0.38);
try {
double value = 0.45;
Widget buildApp({
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Theme(
data: theme,
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Test default track height.
const Radius radius = Radius.circular(trackHeight / 2);
const Radius activatedRadius = Radius.circular((trackHeight + 2) / 2);
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 362.4, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(362.4, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: inactiveTrackColor),
);
// Test default colors for enabled slider.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..shadow(color: shadowColor));
expect(material, paints..circle(color: thumbColor));
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: activeTickMarkColor)));
expect(material, isNot(paints..circle(color: inactiveTickMarkColor)));
// Test defaults colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: activeTickMarkColor)
..circle(color: activeTickMarkColor)
..circle(color: inactiveTickMarkColor)
..circle(color: inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: thumbColor),
);
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
// Test defaults colors for disabled slider.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledInactiveTrackColor)
..rrect(color: disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: shadowColor)..circle(color: disabledThumbColor));
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
// Test defaults colors for disabled discrete slider.
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
expect(
material,
paints
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..shadow(color: shadowColor)
..circle(color: disabledThumbColor),
);
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider uses the right theme colors for the right components', (WidgetTester tester) async { testWidgets('Slider uses the right theme colors for the right components', (WidgetTester tester) async {
debugDisableShadows = false; debugDisableShadows = false;
try { try {
...@@ -279,6 +410,25 @@ void main() { ...@@ -279,6 +410,25 @@ void main() {
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor))); expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test default theme for disabled discrete widget.
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
expect(
material,
paints
..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: sliderTheme.disabledThumbColor),
);
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting the activeColor, inactiveColor and secondaryActiveColor for disabled widget. // Test setting the activeColor, inactiveColor and secondaryActiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3, enabled: false)); await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3, enabled: false));
expect( expect(
...@@ -343,6 +493,60 @@ void main() { ...@@ -343,6 +493,60 @@ void main() {
} }
}); });
testWidgets('Slider parameters overrides theme properties', (WidgetTester tester) async {
debugDisableShadows = false;
const Color activeTrackColor = Color(0xffff0001);
const Color inactiveTrackColor = Color(0xffff0002);
const Color secondaryActiveTrackColor = Color(0xffff0003);
const Color thumbColor = Color(0xffff0004);
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
sliderTheme: const SliderThemeData(
activeTrackColor: Color(0xff000001),
inactiveTickMarkColor: Color(0xff000002),
secondaryActiveTrackColor: Color(0xff000003),
thumbColor: Color(0xff000004),
),
);
try {
const double value = 0.45;
Widget buildApp({ bool enabled = true }) {
return MaterialApp(
theme: theme,
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Slider(
activeColor: activeTrackColor,
inactiveColor: inactiveTrackColor,
secondaryActiveColor: secondaryActiveTrackColor,
thumbColor: thumbColor,
value: value,
secondaryTrackValue: 0.75,
label: '$value',
onChanged: (double value) { },
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Test Slider parameters.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..circle(color: thumbColor));
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async { testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
final ThemeData theme = ThemeData( final ThemeData theme = ThemeData(
platform: TargetPlatform.android, platform: TargetPlatform.android,
...@@ -1652,6 +1856,161 @@ void main() { ...@@ -1652,6 +1856,161 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
}); });
group('Material 2', () {
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
// is turned on by default, these tests can be removed.
testWidgets('Slider defaults', (WidgetTester tester) async {
debugDisableShadows = false;
final ThemeData theme = ThemeData();
const double trackHeight = 4.0;
final ColorScheme colorScheme = theme.colorScheme;
final Color activeTrackColor = Color(colorScheme.primary.value);
final Color inactiveTrackColor = colorScheme.primary.withOpacity(0.24);
final Color secondaryActiveTrackColor = colorScheme.primary.withOpacity(0.54);
final Color disabledActiveTrackColor = colorScheme.onSurface.withOpacity(0.32);
final Color disabledInactiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color disabledSecondaryActiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color shadowColor = colorScheme.shadow;
final Color thumbColor = Color(colorScheme.primary.value);
final Color disabledThumbColor = Color.alphaBlend(colorScheme.onSurface.withOpacity(.38), colorScheme.surface);
final Color activeTickMarkColor = colorScheme.onPrimary.withOpacity(0.54);
final Color inactiveTickMarkColor = colorScheme.primary.withOpacity(0.54);
final Color disabledActiveTickMarkColor = colorScheme.onPrimary.withOpacity(0.12);
final Color disabledInactiveTickMarkColor = colorScheme.onSurface.withOpacity(0.12);
final Color valueIndicatorColor = Color.alphaBlend(colorScheme.onSurface.withOpacity(0.60), colorScheme.surface.withOpacity(0.90));
try {
double value = 0.45;
Widget buildApp({
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
onChanged: onChanged,
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Test default track height.
const Radius radius = Radius.circular(trackHeight / 2);
const Radius activatedRadius = Radius.circular((trackHeight + 2) / 2);
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 362.4, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(362.4, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: inactiveTrackColor),
);
// Test default colors for enabled slider.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..shadow(color: shadowColor));
expect(material, paints..circle(color: thumbColor));
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: activeTickMarkColor)));
expect(material, isNot(paints..circle(color: inactiveTickMarkColor)));
// Test defaults colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: activeTickMarkColor)
..circle(color: activeTickMarkColor)
..circle(color: inactiveTickMarkColor)
..circle(color: inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: thumbColor),
);
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
// Test defaults colors for disabled slider.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledInactiveTrackColor)
..rrect(color: disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: disabledThumbColor));
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
// Test defaults colors for disabled discrete slider.
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
expect(
material,
paints
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: disabledThumbColor),
);
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: activeTickMarkColor)));
expect(material, isNot(paints..circle(color: inactiveTickMarkColor)));
// Test the default color for value indicator.
await tester.pumpWidget(buildApp(divisions: 3));
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..path(color: valueIndicatorColor)
..paragraph(),
);
await gesture.up();
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
} finally {
debugDisableShadows = true;
}
});
});
} }
class RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight extends RoundedRectSliderTrackShape { class RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight extends RoundedRectSliderTrackShape {
...@@ -1681,6 +2040,7 @@ Widget _buildApp( ...@@ -1681,6 +2040,7 @@ Widget _buildApp(
double? secondaryTrackValue, double? secondaryTrackValue,
bool enabled = true, bool enabled = true,
int? divisions, int? divisions,
FocusNode? focusNode,
}) { }) {
final ValueChanged<double>? onChanged = enabled ? (double d) => value = d : null; final ValueChanged<double>? onChanged = enabled ? (double d) => value = d : null;
return MaterialApp( return MaterialApp(
...@@ -1694,6 +2054,7 @@ Widget _buildApp( ...@@ -1694,6 +2054,7 @@ Widget _buildApp(
label: '$value', label: '$value',
onChanged: onChanged, onChanged: onChanged,
divisions: divisions, divisions: divisions,
focusNode: focusNode,
), ),
), ),
), ),
......
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