mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
3720 lines
132 KiB
Dart
3720 lines
132 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
test('SliderThemeData copyWith, ==, hashCode basics', () {
|
|
expect(const SliderThemeData(), const SliderThemeData().copyWith());
|
|
expect(const SliderThemeData().hashCode, const SliderThemeData().copyWith().hashCode);
|
|
});
|
|
|
|
test('SliderThemeData lerp special cases', () {
|
|
const data = SliderThemeData();
|
|
expect(identical(SliderThemeData.lerp(data, data, 0.5), data), true);
|
|
});
|
|
|
|
testWidgets('Default SliderThemeData debugFillProperties', (WidgetTester tester) async {
|
|
final builder = DiagnosticPropertiesBuilder();
|
|
const SliderThemeData().debugFillProperties(builder);
|
|
|
|
final List<String> description = builder.properties
|
|
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
|
.map((DiagnosticsNode node) => node.toString())
|
|
.toList();
|
|
|
|
expect(description, <String>[]);
|
|
});
|
|
|
|
testWidgets('SliderThemeData implements debugFillProperties', (WidgetTester tester) async {
|
|
final builder = DiagnosticPropertiesBuilder();
|
|
const SliderThemeData(
|
|
trackHeight: 7.0,
|
|
activeTrackColor: Color(0xFF000001),
|
|
inactiveTrackColor: Color(0xFF000002),
|
|
secondaryActiveTrackColor: Color(0xFF000003),
|
|
disabledActiveTrackColor: Color(0xFF000004),
|
|
disabledInactiveTrackColor: Color(0xFF000005),
|
|
disabledSecondaryActiveTrackColor: Color(0xFF000006),
|
|
activeTickMarkColor: Color(0xFF000007),
|
|
inactiveTickMarkColor: Color(0xFF000008),
|
|
disabledActiveTickMarkColor: Color(0xFF000009),
|
|
disabledInactiveTickMarkColor: Color(0xFF000010),
|
|
thumbColor: Color(0xFF000011),
|
|
overlappingShapeStrokeColor: Color(0xFF000012),
|
|
disabledThumbColor: Color(0xFF000013),
|
|
overlayColor: Color(0xFF000014),
|
|
valueIndicatorColor: Color(0xFF000015),
|
|
valueIndicatorStrokeColor: Color(0xFF000015),
|
|
overlayShape: RoundSliderOverlayShape(),
|
|
tickMarkShape: RoundSliderTickMarkShape(),
|
|
thumbShape: RoundSliderThumbShape(),
|
|
trackShape: RoundedRectSliderTrackShape(),
|
|
valueIndicatorShape: PaddleSliderValueIndicatorShape(),
|
|
rangeTickMarkShape: RoundRangeSliderTickMarkShape(),
|
|
rangeThumbShape: RoundRangeSliderThumbShape(),
|
|
rangeTrackShape: RoundedRectRangeSliderTrackShape(),
|
|
rangeValueIndicatorShape: PaddleRangeSliderValueIndicatorShape(),
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorTextStyle: TextStyle(color: Colors.black),
|
|
mouseCursor: WidgetStateMouseCursor.clickable,
|
|
allowedInteraction: SliderInteraction.tapOnly,
|
|
padding: EdgeInsets.all(1.0),
|
|
thumbSize: WidgetStatePropertyAll<Size>(Size(20, 20)),
|
|
trackGap: 10.0,
|
|
year2023: false,
|
|
).debugFillProperties(builder);
|
|
|
|
final List<String> description = builder.properties
|
|
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
|
.map((DiagnosticsNode node) => node.toString())
|
|
.toList();
|
|
|
|
expect(description, <String>[
|
|
'trackHeight: 7.0',
|
|
'activeTrackColor: ${const Color(0xff000001)}',
|
|
'inactiveTrackColor: ${const Color(0xff000002)}',
|
|
'secondaryActiveTrackColor: ${const Color(0xff000003)}',
|
|
'disabledActiveTrackColor: ${const Color(0xff000004)}',
|
|
'disabledInactiveTrackColor: ${const Color(0xff000005)}',
|
|
'disabledSecondaryActiveTrackColor: ${const Color(0xff000006)}',
|
|
'activeTickMarkColor: ${const Color(0xff000007)}',
|
|
'inactiveTickMarkColor: ${const Color(0xff000008)}',
|
|
'disabledActiveTickMarkColor: ${const Color(0xff000009)}',
|
|
'disabledInactiveTickMarkColor: ${const Color(0xff000010)}',
|
|
'thumbColor: ${const Color(0xff000011)}',
|
|
'overlappingShapeStrokeColor: ${const Color(0xff000012)}',
|
|
'disabledThumbColor: ${const Color(0xff000013)}',
|
|
'overlayColor: ${const Color(0xff000014)}',
|
|
'valueIndicatorColor: ${const Color(0xff000015)}',
|
|
'valueIndicatorStrokeColor: ${const Color(0xff000015)}',
|
|
"overlayShape: Instance of 'RoundSliderOverlayShape'",
|
|
"tickMarkShape: Instance of 'RoundSliderTickMarkShape'",
|
|
"thumbShape: Instance of 'RoundSliderThumbShape'",
|
|
"trackShape: Instance of 'RoundedRectSliderTrackShape'",
|
|
"valueIndicatorShape: Instance of 'PaddleSliderValueIndicatorShape'",
|
|
"rangeTickMarkShape: Instance of 'RoundRangeSliderTickMarkShape'",
|
|
"rangeThumbShape: Instance of 'RoundRangeSliderThumbShape'",
|
|
"rangeTrackShape: Instance of 'RoundedRectRangeSliderTrackShape'",
|
|
"rangeValueIndicatorShape: Instance of 'PaddleRangeSliderValueIndicatorShape'",
|
|
'showValueIndicator: always',
|
|
'valueIndicatorTextStyle: TextStyle(inherit: true, color: ${const Color(0xff000000)})',
|
|
'mouseCursor: WidgetStateMouseCursor(clickable)',
|
|
'allowedInteraction: tapOnly',
|
|
'padding: EdgeInsets.all(1.0)',
|
|
'thumbSize: WidgetStatePropertyAll(Size(20.0, 20.0))',
|
|
'trackGap: 10.0',
|
|
'year2023: false',
|
|
]);
|
|
});
|
|
|
|
testWidgets('Slider defaults', (WidgetTester tester) async {
|
|
debugDisableShadows = false;
|
|
final theme = ThemeData();
|
|
final ColorScheme colorScheme = theme.colorScheme;
|
|
const trackHeight = 4.0;
|
|
final activeTrackColor = Color(colorScheme.primary.value);
|
|
final Color inactiveTrackColor = colorScheme.surfaceContainerHighest;
|
|
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 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 {
|
|
var 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.circular(trackHeight / 2);
|
|
const activatedRadius = Radius.circular((trackHeight + 2) / 2);
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(360.4, 298.0, 776.0, 302.0, radius),
|
|
color: inactiveTrackColor,
|
|
)
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 297.0, 364.4, 303.0, activatedRadius),
|
|
color: activeTrackColor,
|
|
),
|
|
);
|
|
|
|
// Test default colors for enabled slider.
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: inactiveTrackColor)
|
|
..rrect(color: activeTrackColor)
|
|
..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: inactiveTrackColor)
|
|
..rrect(color: activeTrackColor)
|
|
..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: disabledInactiveTrackColor)
|
|
..rrect(color: disabledActiveTrackColor)
|
|
..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 {
|
|
debugDisableShadows = false;
|
|
try {
|
|
const customColor1 = Color(0xcafefeed);
|
|
const customColor2 = Color(0xdeadbeef);
|
|
const customColor3 = Color(0xdecaface);
|
|
final theme = ThemeData(
|
|
useMaterial3: false,
|
|
platform: TargetPlatform.android,
|
|
primarySwatch: Colors.blue,
|
|
sliderTheme: const SliderThemeData(
|
|
disabledThumbColor: Color(0xff000001),
|
|
disabledActiveTickMarkColor: Color(0xff000002),
|
|
disabledActiveTrackColor: Color(0xff000003),
|
|
disabledInactiveTickMarkColor: Color(0xff000004),
|
|
disabledInactiveTrackColor: Color(0xff000005),
|
|
activeTrackColor: Color(0xff000006),
|
|
activeTickMarkColor: Color(0xff000007),
|
|
inactiveTrackColor: Color(0xff000008),
|
|
inactiveTickMarkColor: Color(0xff000009),
|
|
overlayColor: Color(0xff000010),
|
|
thumbColor: Color(0xff000011),
|
|
valueIndicatorColor: Color(0xff000012),
|
|
disabledSecondaryActiveTrackColor: Color(0xff000013),
|
|
secondaryActiveTrackColor: Color(0xff000014),
|
|
),
|
|
);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme;
|
|
var value = 0.45;
|
|
Widget buildApp({
|
|
Color? activeColor,
|
|
Color? inactiveColor,
|
|
Color? secondaryActiveColor,
|
|
int? divisions,
|
|
bool enabled = true,
|
|
}) {
|
|
final ValueChanged<double>? onChanged = !enabled
|
|
? null
|
|
: (double d) {
|
|
value = d;
|
|
};
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(
|
|
child: Theme(
|
|
data: theme,
|
|
child: Slider(
|
|
value: value,
|
|
secondaryTrackValue: 0.75,
|
|
label: '$value',
|
|
divisions: divisions,
|
|
activeColor: activeColor,
|
|
inactiveColor: inactiveColor,
|
|
secondaryActiveColor: secondaryActiveColor,
|
|
onChanged: onChanged,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
// Check default theme for enabled widget.
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: sliderTheme.inactiveTrackColor)
|
|
..rrect(color: sliderTheme.activeTrackColor)
|
|
..rrect(color: sliderTheme.secondaryActiveTrackColor),
|
|
);
|
|
expect(material, paints..shadow(color: const Color(0xff000000)));
|
|
expect(material, paints..circle(color: sliderTheme.thumbColor));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
|
|
|
|
// Test setting only the activeColor.
|
|
await tester.pumpWidget(buildApp(activeColor: customColor1));
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: sliderTheme.inactiveTrackColor)
|
|
..rrect(color: customColor1)
|
|
..rrect(color: sliderTheme.secondaryActiveTrackColor),
|
|
);
|
|
expect(material, paints..shadow(color: Colors.black));
|
|
expect(material, paints..circle(color: customColor1));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
|
|
// Test setting only the inactiveColor.
|
|
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: customColor1)
|
|
..rrect(color: sliderTheme.activeTrackColor)
|
|
..rrect(color: sliderTheme.secondaryActiveTrackColor),
|
|
);
|
|
expect(material, paints..shadow(color: Colors.black));
|
|
expect(material, paints..circle(color: sliderTheme.thumbColor));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
|
|
// Test setting only the secondaryActiveColor.
|
|
await tester.pumpWidget(buildApp(secondaryActiveColor: customColor1));
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: sliderTheme.inactiveTrackColor)
|
|
..rrect(color: sliderTheme.activeTrackColor)
|
|
..rrect(color: customColor1),
|
|
);
|
|
expect(material, paints..shadow(color: Colors.black));
|
|
expect(material, paints..circle(color: sliderTheme.thumbColor));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
|
|
// Test setting both activeColor, inactiveColor, and secondaryActiveColor.
|
|
await tester.pumpWidget(
|
|
buildApp(
|
|
activeColor: customColor1,
|
|
inactiveColor: customColor2,
|
|
secondaryActiveColor: customColor3,
|
|
),
|
|
);
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: customColor2)
|
|
..rrect(color: customColor1)
|
|
..rrect(color: customColor3),
|
|
);
|
|
expect(material, paints..shadow(color: Colors.black));
|
|
expect(material, paints..circle(color: customColor1));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
|
|
// Test colors for discrete slider.
|
|
await tester.pumpWidget(buildApp(divisions: 3));
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: sliderTheme.inactiveTrackColor)
|
|
..rrect(color: sliderTheme.activeTrackColor)
|
|
..rrect(color: sliderTheme.secondaryActiveTrackColor),
|
|
);
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(color: sliderTheme.activeTickMarkColor)
|
|
..circle(color: sliderTheme.activeTickMarkColor)
|
|
..circle(color: sliderTheme.inactiveTickMarkColor)
|
|
..circle(color: sliderTheme.inactiveTickMarkColor)
|
|
..shadow(color: Colors.black)
|
|
..circle(color: sliderTheme.thumbColor),
|
|
);
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
|
|
// Test colors for discrete slider with inactiveColor and activeColor set.
|
|
await tester.pumpWidget(
|
|
buildApp(
|
|
activeColor: customColor1,
|
|
inactiveColor: customColor2,
|
|
secondaryActiveColor: customColor3,
|
|
divisions: 3,
|
|
),
|
|
);
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: customColor2)
|
|
..rrect(color: customColor1)
|
|
..rrect(color: customColor3),
|
|
);
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(color: customColor2)
|
|
..circle(color: customColor2)
|
|
..circle(color: customColor1)
|
|
..circle(color: customColor1)
|
|
..shadow(color: Colors.black)
|
|
..circle(color: customColor1),
|
|
);
|
|
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
|
|
|
|
// Test default theme for disabled widget.
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: sliderTheme.disabledInactiveTrackColor)
|
|
..rrect(color: sliderTheme.disabledActiveTrackColor)
|
|
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
|
|
);
|
|
expect(
|
|
material,
|
|
paints
|
|
..shadow(color: Colors.black)
|
|
..circle(color: sliderTheme.disabledThumbColor),
|
|
);
|
|
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
|
|
// These 2 colors are too close to distinguish.
|
|
// expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
|
|
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)));
|
|
// These 2 colors are too close to distinguish.
|
|
// 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.
|
|
await tester.pumpWidget(
|
|
buildApp(
|
|
activeColor: customColor1,
|
|
inactiveColor: customColor2,
|
|
secondaryActiveColor: customColor3,
|
|
enabled: false,
|
|
),
|
|
);
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: sliderTheme.disabledInactiveTrackColor)
|
|
..rrect(color: sliderTheme.disabledActiveTrackColor)
|
|
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
|
|
);
|
|
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
|
|
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
|
|
// These colors are too close to distinguish.
|
|
// expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
|
|
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
|
|
|
|
// Test that the default value indicator has the right colors.
|
|
await tester.pumpWidget(buildApp(divisions: 3));
|
|
Offset center = tester.getCenter(find.byType(Slider));
|
|
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: sliderTheme.valueIndicatorColor)
|
|
..paragraph(),
|
|
);
|
|
await gesture.up();
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
// Testing the custom colors are used for the indicator.
|
|
await tester.pumpWidget(
|
|
buildApp(divisions: 3, activeColor: customColor1, inactiveColor: customColor2),
|
|
);
|
|
center = tester.getCenter(find.byType(Slider));
|
|
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
|
|
..rrect(color: const Color(0xfffafafa))
|
|
..rrect(color: customColor2) // Inactive track
|
|
..rrect(color: customColor1) // Active track
|
|
..circle(color: customColor1.withOpacity(0.12)) // overlay
|
|
..circle(color: customColor2) // 1st tick mark
|
|
..circle(color: customColor2) // 2nd tick mark
|
|
..circle(color: customColor2) // 3rd tick mark
|
|
..circle(color: customColor1) // 4th tick mark
|
|
..shadow(color: Colors.black)
|
|
..circle(color: customColor1) // thumb
|
|
..path(color: sliderTheme.valueIndicatorColor), // indicator
|
|
);
|
|
await gesture.up();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets('Slider parameters overrides theme properties', (WidgetTester tester) async {
|
|
debugDisableShadows = false;
|
|
const activeTrackColor = Color(0xffff0001);
|
|
const inactiveTrackColor = Color(0xffff0002);
|
|
const secondaryActiveTrackColor = Color(0xffff0003);
|
|
const thumbColor = Color(0xffff0004);
|
|
|
|
final 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 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: inactiveTrackColor)
|
|
..rrect(color: activeTrackColor)
|
|
..rrect(color: secondaryActiveTrackColor),
|
|
);
|
|
expect(material, paints..circle(color: thumbColor));
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.red);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme;
|
|
final SliderThemeData customTheme = sliderTheme.copyWith(
|
|
activeTrackColor: Colors.purple,
|
|
inactiveTrackColor: Colors.purple.withAlpha(0x3d),
|
|
secondaryActiveTrackColor: Colors.purple.withAlpha(0x8a),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false),
|
|
);
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: customTheme.disabledActiveTrackColor)
|
|
..rrect(color: customTheme.disabledInactiveTrackColor)
|
|
..rrect(color: customTheme.disabledSecondaryActiveTrackColor),
|
|
);
|
|
});
|
|
|
|
testWidgets('Slider overrides ThemeData theme if SliderTheme present', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.red);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme;
|
|
final SliderThemeData customTheme = sliderTheme.copyWith(
|
|
activeTrackColor: Colors.purple,
|
|
inactiveTrackColor: Colors.purple.withAlpha(0x3d),
|
|
secondaryActiveTrackColor: Colors.purple.withAlpha(0x8a),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false),
|
|
);
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: customTheme.disabledActiveTrackColor)
|
|
..rrect(color: customTheme.disabledInactiveTrackColor)
|
|
..rrect(color: customTheme.disabledSecondaryActiveTrackColor),
|
|
);
|
|
});
|
|
|
|
testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const customColor1 = Color(0xcafefeed);
|
|
const customColor2 = Color(0xdeadbeef);
|
|
const customColor3 = Color(0xdecaface);
|
|
const customColor4 = Color(0xfeedcafe);
|
|
|
|
final sliderTheme = SliderThemeData.fromPrimaryColors(
|
|
primaryColor: customColor1,
|
|
primaryColorDark: customColor2,
|
|
primaryColorLight: customColor3,
|
|
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(
|
|
color: customColor4,
|
|
),
|
|
);
|
|
|
|
expect(sliderTheme.activeTrackColor, equals(customColor1.withAlpha(0xff)));
|
|
expect(sliderTheme.inactiveTrackColor, equals(customColor1.withAlpha(0x3d)));
|
|
expect(sliderTheme.secondaryActiveTrackColor, equals(customColor1.withAlpha(0x8a)));
|
|
expect(sliderTheme.disabledActiveTrackColor, equals(customColor2.withAlpha(0x52)));
|
|
expect(sliderTheme.disabledInactiveTrackColor, equals(customColor2.withAlpha(0x1f)));
|
|
expect(sliderTheme.disabledSecondaryActiveTrackColor, equals(customColor2.withAlpha(0x1f)));
|
|
expect(sliderTheme.activeTickMarkColor, equals(customColor3.withAlpha(0x8a)));
|
|
expect(sliderTheme.inactiveTickMarkColor, equals(customColor1.withAlpha(0x8a)));
|
|
expect(sliderTheme.disabledActiveTickMarkColor, equals(customColor3.withAlpha(0x1f)));
|
|
expect(sliderTheme.disabledInactiveTickMarkColor, equals(customColor2.withAlpha(0x1f)));
|
|
expect(sliderTheme.thumbColor, equals(customColor1.withAlpha(0xff)));
|
|
expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52)));
|
|
expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x1f)));
|
|
expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
|
|
expect(sliderTheme.valueIndicatorStrokeColor, equals(customColor1.withAlpha(0xff)));
|
|
expect(sliderTheme.valueIndicatorTextStyle!.color, equals(customColor4));
|
|
});
|
|
|
|
testWidgets('SliderThemeData generates correct shapes for fromPrimaryColors', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const customColor1 = Color(0xcafefeed);
|
|
const customColor2 = Color(0xdeadbeef);
|
|
const customColor3 = Color(0xdecaface);
|
|
const customColor4 = Color(0xfeedcafe);
|
|
|
|
final sliderTheme = SliderThemeData.fromPrimaryColors(
|
|
primaryColor: customColor1,
|
|
primaryColorDark: customColor2,
|
|
primaryColorLight: customColor3,
|
|
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(
|
|
color: customColor4,
|
|
),
|
|
);
|
|
|
|
expect(sliderTheme.overlayShape, const RoundSliderOverlayShape());
|
|
expect(sliderTheme.tickMarkShape, const RoundSliderTickMarkShape());
|
|
expect(sliderTheme.thumbShape, const RoundSliderThumbShape());
|
|
expect(sliderTheme.trackShape, const RoundedRectSliderTrackShape());
|
|
expect(sliderTheme.valueIndicatorShape, const PaddleSliderValueIndicatorShape());
|
|
expect(sliderTheme.rangeTickMarkShape, const RoundRangeSliderTickMarkShape());
|
|
expect(sliderTheme.rangeThumbShape, const RoundRangeSliderThumbShape());
|
|
expect(sliderTheme.rangeTrackShape, const RoundedRectRangeSliderTrackShape());
|
|
expect(sliderTheme.rangeValueIndicatorShape, const PaddleRangeSliderValueIndicatorShape());
|
|
});
|
|
|
|
testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async {
|
|
final SliderThemeData sliderThemeBlack = SliderThemeData.fromPrimaryColors(
|
|
primaryColor: Colors.black,
|
|
primaryColorDark: Colors.black,
|
|
primaryColorLight: Colors.black,
|
|
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(
|
|
color: Colors.black,
|
|
),
|
|
).copyWith(trackHeight: 2.0);
|
|
final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors(
|
|
primaryColor: Colors.white,
|
|
primaryColorDark: Colors.white,
|
|
primaryColorLight: Colors.white,
|
|
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(
|
|
color: Colors.white,
|
|
),
|
|
).copyWith(trackHeight: 6.0);
|
|
final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
|
|
const middleGrey = Color(0xff7f7f7f);
|
|
|
|
expect(lerp.trackHeight, equals(4.0));
|
|
expect(lerp.activeTrackColor, isSameColorAs(middleGrey.withAlpha(0xff)));
|
|
expect(lerp.inactiveTrackColor, isSameColorAs(middleGrey.withAlpha(0x3d)));
|
|
expect(lerp.secondaryActiveTrackColor, isSameColorAs(middleGrey.withAlpha(0x8a)));
|
|
expect(lerp.disabledActiveTrackColor, isSameColorAs(middleGrey.withAlpha(0x52)));
|
|
expect(lerp.disabledInactiveTrackColor, isSameColorAs(middleGrey.withAlpha(0x1f)));
|
|
expect(lerp.disabledSecondaryActiveTrackColor, isSameColorAs(middleGrey.withAlpha(0x1f)));
|
|
expect(lerp.activeTickMarkColor, isSameColorAs(middleGrey.withAlpha(0x8a)));
|
|
expect(lerp.inactiveTickMarkColor, isSameColorAs(middleGrey.withAlpha(0x8a)));
|
|
expect(lerp.disabledActiveTickMarkColor, isSameColorAs(middleGrey.withAlpha(0x1f)));
|
|
expect(lerp.disabledInactiveTickMarkColor, isSameColorAs(middleGrey.withAlpha(0x1f)));
|
|
expect(lerp.thumbColor, isSameColorAs(middleGrey.withAlpha(0xff)));
|
|
expect(lerp.disabledThumbColor, isSameColorAs(middleGrey.withAlpha(0x52)));
|
|
expect(lerp.overlayColor, isSameColorAs(middleGrey.withAlpha(0x1f)));
|
|
expect(lerp.valueIndicatorColor, isSameColorAs(middleGrey.withAlpha(0xff)));
|
|
expect(lerp.valueIndicatorStrokeColor, isSameColorAs(middleGrey.withAlpha(0xff)));
|
|
expect(lerp.valueIndicatorTextStyle!.color, isSameColorAs(middleGrey.withAlpha(0xff)));
|
|
});
|
|
|
|
testWidgets('Default slider track draws correctly', (WidgetTester tester) async {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.blue);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5));
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
const radius = Radius.circular(2);
|
|
const activatedRadius = Radius.circular(3);
|
|
|
|
// The enabled slider thumb has track segments that extend to and from
|
|
// the center of the thumb.
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(210.0, 298.0, 776.0, 302.0, radius),
|
|
color: sliderTheme.inactiveTrackColor,
|
|
)
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 297.0, 214.0, 303.0, activatedRadius),
|
|
color: sliderTheme.activeTrackColor,
|
|
)
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
212.0,
|
|
298.0,
|
|
400.0,
|
|
302.0,
|
|
topRight: radius,
|
|
bottomRight: radius,
|
|
),
|
|
color: sliderTheme.secondaryActiveTrackColor,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5, enabled: false),
|
|
);
|
|
await tester.pumpAndSettle(); // wait for disable animation
|
|
|
|
// The disabled slider thumb is the same size as the enabled thumb.
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(210.0, 298.0, 776.0, 302.0, radius),
|
|
color: sliderTheme.disabledInactiveTrackColor,
|
|
)
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 297.0, 214.0, 303.0, activatedRadius),
|
|
color: sliderTheme.disabledActiveTrackColor,
|
|
)
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
212.0,
|
|
298.0,
|
|
400.0,
|
|
302.0,
|
|
topRight: radius,
|
|
bottomRight: radius,
|
|
),
|
|
color: sliderTheme.disabledSecondaryActiveTrackColor,
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Default slider overlay draws correctly', (WidgetTester tester) async {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.blue);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// With no touch, paints only the thumb.
|
|
expect(
|
|
material,
|
|
paints..circle(color: sliderTheme.thumbColor, x: 212.0, y: 300.0, radius: 10.0),
|
|
);
|
|
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for overlay animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
// After touch, paints thumb and overlay.
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(color: sliderTheme.overlayColor, x: 212.0, y: 300.0, radius: 24.0)
|
|
..circle(color: sliderTheme.thumbColor, x: 212.0, y: 300.0, radius: 10.0),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// After the gesture is up and complete, it again paints only the thumb.
|
|
expect(
|
|
material,
|
|
paints..circle(color: sliderTheme.thumbColor, x: 212.0, y: 300.0, radius: 10.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('Slider can use theme overlay with material states', (WidgetTester tester) async {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.blue);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
|
|
overlayColor: WidgetStateColor.resolveWith((Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.focused)) {
|
|
return Colors.brown[500]!;
|
|
}
|
|
|
|
return Colors.transparent;
|
|
}),
|
|
);
|
|
final focusNode = FocusNode(debugLabel: 'Slider');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = 0.5;
|
|
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: sliderTheme),
|
|
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: Colors.brown[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.brown[500])),
|
|
);
|
|
});
|
|
|
|
testWidgets('Default slider ticker and thumb shape draw correctly', (WidgetTester tester) async {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.blue);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45));
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(material, paints..circle(color: sliderTheme.thumbColor, radius: 10.0));
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, enabled: false));
|
|
await tester.pumpAndSettle(); // wait for disable animation
|
|
|
|
expect(material, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0));
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3));
|
|
await tester.pumpAndSettle(); // wait for enable animation
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(color: sliderTheme.activeTickMarkColor)
|
|
..circle(color: sliderTheme.activeTickMarkColor)
|
|
..circle(color: sliderTheme.inactiveTickMarkColor)
|
|
..circle(color: sliderTheme.inactiveTickMarkColor)
|
|
..circle(color: sliderTheme.thumbColor, radius: 10.0),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3, enabled: false));
|
|
await tester.pumpAndSettle(); // wait for disable animation
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(color: sliderTheme.disabledActiveTickMarkColor)
|
|
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
|
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
|
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
|
|
..circle(color: sliderTheme.disabledThumbColor, radius: 10.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('Default paddle slider value indicator shape draws correctly', (
|
|
WidgetTester tester,
|
|
) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
final theme = ThemeData(
|
|
useMaterial3: false,
|
|
platform: TargetPlatform.android,
|
|
primarySwatch: Colors.blue,
|
|
);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
|
|
thumbColor: Colors.red.shade500,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
|
|
);
|
|
Widget buildApp(
|
|
String value, {
|
|
double sliderValue = 0.5,
|
|
TextScaler textScaler = TextScaler.noScaling,
|
|
}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Material(
|
|
child: Row(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: SliderTheme(
|
|
data: sliderTheme,
|
|
child: Slider(
|
|
value: sliderValue,
|
|
label: value,
|
|
divisions: 3,
|
|
onChanged: (double d) {},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp('1'));
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
Offset center = tester.getCenter(find.byType(Slider));
|
|
TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(15.9, -40.0),
|
|
const Offset(-15.9, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
|
|
),
|
|
);
|
|
|
|
await gesture.up();
|
|
|
|
// Test that it expands with a larger label.
|
|
await tester.pumpWidget(buildApp('1000'));
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(35.9, -40.0),
|
|
const Offset(-35.9, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that it avoids the left edge of the screen.
|
|
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(92.0, -40.0),
|
|
const Offset(-16.0, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-20.1, -40.0)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that it avoids the right edge of the screen.
|
|
await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(16.0, -40.0),
|
|
const Offset(-92.0, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(20.1, -40.0), const Offset(-98.1, -40.0)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that the neck stretches when the text scale gets smaller.
|
|
await tester.pumpWidget(
|
|
buildApp('1000000', sliderValue: 0.0, textScaler: const TextScaler.linear(0.5)),
|
|
);
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -49.0),
|
|
const Offset(68.0, -49.0),
|
|
const Offset(-24.0, -49.0),
|
|
],
|
|
excludes: <Offset>[
|
|
const Offset(98.0, -32.0), // inside full size, outside small
|
|
const Offset(-40.0, -32.0), // inside full size, outside small
|
|
const Offset(90.1, -49.0),
|
|
const Offset(-40.1, -49.0),
|
|
],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that the neck shrinks when the text scale gets larger.
|
|
await tester.pumpWidget(
|
|
buildApp('1000000', sliderValue: 0.0, textScaler: const TextScaler.linear(2.5)),
|
|
);
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -38.8),
|
|
const Offset(92.0, -38.8),
|
|
const Offset(8.0, -23.0), // Inside large, outside scale=1.0
|
|
const Offset(-2.0, -23.0), // Inside large, outside scale=1.0
|
|
],
|
|
excludes: <Offset>[const Offset(98.5, -38.8), const Offset(-16.1, -38.8)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets('Default paddle slider value indicator shape draws correctly', (
|
|
WidgetTester tester,
|
|
) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
final theme = ThemeData(
|
|
useMaterial3: false,
|
|
platform: TargetPlatform.android,
|
|
primarySwatch: Colors.blue,
|
|
);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
|
|
thumbColor: Colors.red.shade500,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
|
|
);
|
|
Widget buildApp(
|
|
String value, {
|
|
double sliderValue = 0.5,
|
|
TextScaler textScaler = TextScaler.noScaling,
|
|
}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Material(
|
|
child: Row(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: SliderTheme(
|
|
data: sliderTheme,
|
|
child: Slider(
|
|
value: sliderValue,
|
|
label: value,
|
|
divisions: 3,
|
|
onChanged: (double d) {},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp('1'));
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
Offset center = tester.getCenter(find.byType(Slider));
|
|
TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(15.9, -40.0),
|
|
const Offset(-15.9, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
|
|
),
|
|
);
|
|
|
|
await gesture.up();
|
|
|
|
// Test that it expands with a larger label.
|
|
await tester.pumpWidget(buildApp('1000'));
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(35.9, -40.0),
|
|
const Offset(-35.9, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that it avoids the left edge of the screen.
|
|
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(92.0, -40.0),
|
|
const Offset(-16.0, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-20.1, -40.0)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that it avoids the right edge of the screen.
|
|
await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -40.0),
|
|
const Offset(16.0, -40.0),
|
|
const Offset(-92.0, -40.0),
|
|
],
|
|
excludes: <Offset>[const Offset(20.1, -40.0), const Offset(-98.1, -40.0)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that the neck stretches when the text scale gets smaller.
|
|
await tester.pumpWidget(
|
|
buildApp('1000000', sliderValue: 0.0, textScaler: const TextScaler.linear(0.5)),
|
|
);
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -49.0),
|
|
const Offset(68.0, -49.0),
|
|
const Offset(-24.0, -49.0),
|
|
],
|
|
excludes: <Offset>[
|
|
const Offset(98.0, -32.0), // inside full size, outside small
|
|
const Offset(-40.0, -32.0), // inside full size, outside small
|
|
const Offset(90.1, -49.0),
|
|
const Offset(-40.1, -49.0),
|
|
],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
|
|
// Test that the neck shrinks when the text scale gets larger.
|
|
await tester.pumpWidget(
|
|
buildApp('1000000', sliderValue: 0.0, textScaler: const TextScaler.linear(2.5)),
|
|
);
|
|
center = tester.getCenter(find.byType(Slider));
|
|
gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints..path(
|
|
color: sliderTheme.valueIndicatorColor,
|
|
includes: <Offset>[
|
|
const Offset(0.0, -38.8),
|
|
const Offset(92.0, -38.8),
|
|
const Offset(8.0, -23.0), // Inside large, outside scale=1.0
|
|
const Offset(-2.0, -23.0), // Inside large, outside scale=1.0
|
|
],
|
|
excludes: <Offset>[const Offset(98.5, -38.8), const Offset(-16.1, -38.8)],
|
|
),
|
|
);
|
|
await gesture.up();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets('The slider track height can be overridden', (WidgetTester tester) async {
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(trackHeight: 16);
|
|
const radius = Radius.circular(8);
|
|
const activatedRadius = Radius.circular(9);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Top and bottom are centerY (300) + and - trackRadius (8).
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(204.0, 292.0, 776.0, 308.0, radius),
|
|
color: sliderTheme.inactiveTrackColor,
|
|
)
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 291.0, 220.0, 309.0, activatedRadius),
|
|
color: sliderTheme.activeTrackColor,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
|
|
await tester.pumpAndSettle(); // wait for disable animation
|
|
|
|
// The disabled thumb is smaller so the active track has to paint longer to
|
|
// get to the edge.
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(204.0, 292.0, 776.0, 308.0, radius),
|
|
color: sliderTheme.disabledInactiveTrackColor,
|
|
)
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 291.0, 220.0, 309.0, activatedRadius),
|
|
color: sliderTheme.disabledActiveTrackColor,
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('The default slider thumb shape sizes can be overridden', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
|
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 7, disabledThumbRadius: 11),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(material, paints..circle(x: 212, y: 300, radius: 7, color: sliderTheme.thumbColor));
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
|
|
await tester.pumpAndSettle(); // wait for disable animation
|
|
|
|
expect(
|
|
material,
|
|
paints..circle(x: 212, y: 300, radius: 11, color: sliderTheme.disabledThumbColor),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'The default slider thumb shape disabled size can be inferred from the enabled size',
|
|
(WidgetTester tester) async {
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
|
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 9),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(material, paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.thumbColor));
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
|
|
await tester.pumpAndSettle(); // wait for disable animation
|
|
expect(
|
|
material,
|
|
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.disabledThumbColor),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('The default slider tick mark shape size can be overridden', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
|
|
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5),
|
|
activeTickMarkColor: const Color(0xfadedead),
|
|
inactiveTickMarkColor: const Color(0xfadebeef),
|
|
disabledActiveTickMarkColor: const Color(0xfadecafe),
|
|
disabledInactiveTickMarkColor: const Color(0xfadeface),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2));
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
|
|
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
|
|
..circle(x: 774, y: 300, radius: 5, color: sliderTheme.inactiveTickMarkColor),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, enabled: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
|
|
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
|
|
..circle(x: 774, y: 300, radius: 5, color: sliderTheme.disabledInactiveTickMarkColor),
|
|
);
|
|
});
|
|
|
|
testWidgets('The default slider overlay shape size can be overridden', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const double uniqueOverlayRadius = 23;
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
|
|
overlayShape: const RoundSliderOverlayShape(overlayRadius: uniqueOverlayRadius),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5));
|
|
// Tap center and wait for animation.
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints..circle(
|
|
x: center.dx,
|
|
y: center.dy,
|
|
radius: uniqueOverlayRadius,
|
|
color: sliderTheme.overlayColor,
|
|
),
|
|
);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/74503
|
|
testWidgets(
|
|
'The slider track layout correctly when the overlay size is smaller than the thumb size',
|
|
(WidgetTester tester) async {
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5));
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// The track rectangle begins at 10 pixels from the left of the screen and ends 10 pixels from the right
|
|
// (790 pixels from the left). The main check here it that the track itself should be centered on
|
|
// the 800 pixel-wide screen.
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track RRect. Ends 10 pixels from right of screen.
|
|
..rrect(rrect: RRect.fromLTRBR(398.0, 298.0, 790.0, 302.0, const Radius.circular(2.0)))
|
|
// Active track RRect. Starts 10 pixels from left of screen.
|
|
..rrect(rrect: RRect.fromLTRBR(10.0, 297.0, 402.0, 303.0, const Radius.circular(3.0)))
|
|
// The thumb.
|
|
..circle(x: 400.0, y: 300.0, radius: 10.0),
|
|
);
|
|
},
|
|
);
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/125467
|
|
testWidgets(
|
|
'The RangeSlider track layout correctly when the overlay size is smaller than the thumb size',
|
|
(WidgetTester tester) async {
|
|
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
);
|
|
|
|
await tester.pumpWidget(_buildRangeApp(sliderTheme, values: const RangeValues(0.0, 1.0)));
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(RangeSlider)));
|
|
|
|
// The track rectangle begins at 10 pixels from the left of the screen and ends 10 pixels from the right
|
|
// (790 pixels from the left). The main check here it that the track itself should be centered on
|
|
// the 800 pixel-wide screen.
|
|
expect(
|
|
material,
|
|
paints
|
|
// active track RRect. Starts 10 pixels from left of screen.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
10.0,
|
|
298.0,
|
|
10.0,
|
|
302.0,
|
|
topLeft: const Radius.circular(2.0),
|
|
bottomLeft: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// inactive track RRect. Ends 10 pixels from right of screen.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
790.0,
|
|
298.0,
|
|
790.0,
|
|
302.0,
|
|
topRight: const Radius.circular(2.0),
|
|
bottomRight: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// active track RRect Start 10 pixels from left screen.
|
|
..rrect(rrect: RRect.fromLTRBR(8.0, 297.0, 792.0, 303.0, const Radius.circular(2.0)))
|
|
// The thumb Left.
|
|
..circle(x: 10.0, y: 300.0, radius: 10.0)
|
|
// The thumb Right.
|
|
..circle(x: 790.0, y: 300.0, radius: 10.0),
|
|
);
|
|
},
|
|
);
|
|
|
|
// Only the thumb, overlay, and tick mark have special shortcuts to provide
|
|
// no-op or empty shapes.
|
|
//
|
|
// The track can also be skipped by providing 0 height.
|
|
//
|
|
// The value indicator can be skipped by passing the appropriate
|
|
// [ShowValueIndicator].
|
|
testWidgets('The slider can skip all of its component painting', (WidgetTester tester) async {
|
|
// Pump a slider with all shapes skipped.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.never,
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
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 {
|
|
// Pump a slider with just a track.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.never,
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Only 2 track segments.
|
|
expect(material, paintsExactlyCountTimes(#drawRRect, 2));
|
|
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
|
|
expect(material, paintsExactlyCountTimes(#drawPath, 0));
|
|
});
|
|
|
|
testWidgets('The slider can skip all component painting except the tick marks', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Pump a slider with just tick marks.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
showValueIndicator: ShowValueIndicator.never,
|
|
// When the track is hidden to 0 height, a tick mark radius
|
|
// must be provided to get a non-zero radius.
|
|
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 1),
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Only 5 tick marks.
|
|
expect(material, paintsExactlyCountTimes(#drawRect, 0));
|
|
expect(material, paintsExactlyCountTimes(#drawCircle, 5));
|
|
expect(material, paintsExactlyCountTimes(#drawPath, 0));
|
|
});
|
|
|
|
testWidgets('The slider can skip all component painting except the thumb', (
|
|
WidgetTester tester,
|
|
) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
// Pump a slider with just a thumb.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.never,
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Only 1 thumb.
|
|
expect(material, paintsExactlyCountTimes(#drawRect, 0));
|
|
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
|
|
expect(material, paintsExactlyCountTimes(#drawPath, 0));
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets('The slider can skip all component painting except the overlay', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Pump a slider with just an overlay.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.never,
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Tap the center of the track and wait for animations to finish.
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Only 1 overlay.
|
|
expect(material, paintsExactlyCountTimes(#drawRect, 0));
|
|
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
|
|
expect(material, paintsExactlyCountTimes(#drawPath, 0));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('The slider can skip all component painting except the value indicator', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Pump a slider with just a value indicator.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
// Tap the center of the track and wait for animations to finish.
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Only 1 value indicator.
|
|
expect(material, paintsExactlyCountTimes(#drawRect, 0));
|
|
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('PaddleSliderValueIndicatorShape skips all painting at zero scale', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Pump a slider with just a value indicator.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
// Tap the center of the track to kick off the animation of the value indicator.
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
|
|
// Nothing to paint at scale 0.
|
|
await tester.pump();
|
|
expect(material, paintsExactlyCountTimes(#drawRect, 0));
|
|
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
|
|
|
|
// Painting a path for the value indicator.
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Default slider value indicator shape skips all painting at zero scale', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Pump a slider with just a value indicator.
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.onDrag,
|
|
),
|
|
value: 0.5,
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
// Tap the center of the track to kick off the animation of the value indicator.
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
|
|
// Nothing to paint at scale 0.
|
|
await tester.pump();
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
|
|
|
|
// Painting a path for the value indicator.
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Default paddle range slider value indicator shape draws correctly', (
|
|
WidgetTester tester,
|
|
) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.blue);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
|
|
thumbColor: Colors.red.shade500,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildRangeApp(sliderTheme));
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
// physical model
|
|
..rrect()
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
298.0,
|
|
24.0,
|
|
302.0,
|
|
topLeft: const Radius.circular(2.0),
|
|
bottomLeft: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
298.0,
|
|
776.0,
|
|
302.0,
|
|
topRight: const Radius.circular(2.0),
|
|
bottomRight: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
..rrect(rrect: RRect.fromLTRBR(22.0, 297.0, 26.0, 303.0, const Radius.circular(2.0)))
|
|
..circle(x: 24.0, y: 300.0)
|
|
..shadow(elevation: 1.0)
|
|
..circle(x: 24.0, y: 300.0)
|
|
..shadow(elevation: 6.0)
|
|
..circle(x: 24.0, y: 300.0),
|
|
);
|
|
|
|
await gesture.up();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets(
|
|
'Default paddle range slider value indicator shape draws correctly with debugDisableShadows',
|
|
(WidgetTester tester) async {
|
|
debugDisableShadows = true;
|
|
final theme = ThemeData(platform: TargetPlatform.android, primarySwatch: Colors.blue);
|
|
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
|
|
thumbColor: Colors.red.shade500,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildRangeApp(sliderTheme));
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
// physical model
|
|
..rrect()
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
298.0,
|
|
24.0,
|
|
302.0,
|
|
topLeft: const Radius.circular(2.0),
|
|
bottomLeft: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
298.0,
|
|
776.0,
|
|
302.0,
|
|
topRight: const Radius.circular(2.0),
|
|
bottomRight: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
..rrect(rrect: RRect.fromLTRBR(22.0, 297.0, 26.0, 303.0, const Radius.circular(2)))
|
|
..circle(x: 24.0, y: 300.0)
|
|
..path(strokeWidth: 1.0 * 2.0, color: Colors.black)
|
|
..circle(x: 24.0, y: 300.0)
|
|
..path(strokeWidth: 6.0 * 2.0, color: Colors.black)
|
|
..circle(x: 24.0, y: 300.0),
|
|
);
|
|
|
|
await gesture.up();
|
|
},
|
|
);
|
|
|
|
testWidgets('PaddleRangeSliderValueIndicatorShape skips all painting at zero scale', (
|
|
WidgetTester tester,
|
|
) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
// Pump a slider with just a value indicator.
|
|
await tester.pumpWidget(
|
|
_buildRangeApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
|
|
),
|
|
values: const RangeValues(0, 0.5),
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
// final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider));
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
// Tap the center of the track to kick off the animation of the value indicator.
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
|
|
// No value indicator path to paint at scale 0.
|
|
await tester.pump();
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
|
|
|
|
// Painting a path for each value indicator.
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
|
|
|
|
await gesture.up();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets('Default range indicator shape skips all painting at zero scale', (
|
|
WidgetTester tester,
|
|
) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
// Pump a slider with just a value indicator.
|
|
await tester.pumpWidget(
|
|
_buildRangeApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackHeight: 0,
|
|
overlayShape: SliderComponentShape.noOverlay,
|
|
thumbShape: SliderComponentShape.noThumb,
|
|
tickMarkShape: SliderTickMarkShape.noTickMark,
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
),
|
|
values: const RangeValues(0, 0.5),
|
|
divisions: 4,
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
// Tap the center of the track to kick off the animation of the value indicator.
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
|
|
// No value indicator path to paint at scale 0.
|
|
await tester.pump();
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
|
|
|
|
// Painting a path for each value indicator.
|
|
await tester.pump(const Duration(milliseconds: 16));
|
|
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
|
|
|
|
await gesture.up();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets(
|
|
'activeTrackRadius is taken into account when painting the border of the active track',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
value: 0.5,
|
|
ThemeData().sliderTheme.copyWith(
|
|
trackShape: const RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight(
|
|
additionalActiveTrackHeight: 10.0,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
expect(
|
|
find.byType(Slider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(398.0, 298.0, 776.0, 302.0, const Radius.circular(2.0)))
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(24.0, 293.0, 402.0, 307.0, const Radius.circular(7.0))),
|
|
);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
},
|
|
);
|
|
|
|
testWidgets('The mouse cursor is themeable', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
_buildApp(
|
|
ThemeData().sliderTheme.copyWith(
|
|
mouseCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.text),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.text,
|
|
);
|
|
});
|
|
|
|
testWidgets('SliderTheme.allowedInteraction is themeable', (WidgetTester tester) async {
|
|
var value = 0.0;
|
|
|
|
Widget buildApp({
|
|
bool isAllowedInteractionInThemeNull = false,
|
|
bool isAllowedInteractionInSliderNull = false,
|
|
}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: SliderTheme(
|
|
data: ThemeData().sliderTheme.copyWith(
|
|
allowedInteraction: isAllowedInteractionInThemeNull
|
|
? null
|
|
: SliderInteraction.slideOnly,
|
|
),
|
|
child: StatefulBuilder(
|
|
builder: (_, void Function(void Function()) setState) {
|
|
return Slider(
|
|
value: value,
|
|
allowedInteraction: isAllowedInteractionInSliderNull
|
|
? null
|
|
: SliderInteraction.tapOnly,
|
|
onChanged: (double newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final TestGesture gesture = await tester.createGesture();
|
|
|
|
// when theme and parameter are specified, parameter is used [tapOnly].
|
|
await tester.pumpWidget(buildApp());
|
|
// tap is allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
expect(value, equals(0.5)); // changes
|
|
await gesture.up();
|
|
// slide isn't allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(50, 0));
|
|
expect(value, equals(0.0)); // no change
|
|
await gesture.up();
|
|
|
|
// when only parameter is specified, parameter is used [tapOnly].
|
|
await tester.pumpWidget(buildApp(isAllowedInteractionInThemeNull: true));
|
|
// tap is allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
expect(value, equals(0.5)); // changes
|
|
await gesture.up();
|
|
// slide isn't allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(50, 0));
|
|
expect(value, equals(0.0)); // no change
|
|
await gesture.up();
|
|
|
|
// when theme is specified but parameter is null, theme is used [slideOnly].
|
|
await tester.pumpWidget(buildApp(isAllowedInteractionInSliderNull: true));
|
|
// tap isn't allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
expect(value, equals(0.0)); // no change
|
|
await gesture.up();
|
|
// slide isn't allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(50, 0));
|
|
expect(value, greaterThan(0.0)); // changes
|
|
await gesture.up();
|
|
|
|
// when both theme and parameter are null, default is used [tapAndSlide].
|
|
await tester.pumpWidget(
|
|
buildApp(isAllowedInteractionInSliderNull: true, isAllowedInteractionInThemeNull: true),
|
|
);
|
|
// tap is allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
expect(value, equals(0.5));
|
|
await gesture.up();
|
|
// slide is allowed.
|
|
value = 0.0;
|
|
await gesture.down(tester.getCenter(find.byType(Slider)));
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(50, 0));
|
|
expect(value, greaterThan(0.0)); // changes
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Default value indicator color', (WidgetTester tester) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
final theme = ThemeData(platform: TargetPlatform.android);
|
|
Widget buildApp(
|
|
String value, {
|
|
double sliderValue = 0.5,
|
|
TextScaler textScaler = TextScaler.noScaling,
|
|
}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Material(
|
|
child: Row(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: Slider(
|
|
value: sliderValue,
|
|
label: value,
|
|
divisions: 3,
|
|
onChanged: (double d) {},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp('1'));
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
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(
|
|
valueIndicatorBox,
|
|
paints
|
|
..rrect(color: const Color(0xfffef7ff))
|
|
..rrect(color: const Color(0xffe6e0e9))
|
|
..rrect(color: const Color(0xff6750a4))
|
|
..path(color: Color(theme.colorScheme.primary.value)),
|
|
);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
|
|
testWidgets(
|
|
'RectangularSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor',
|
|
(WidgetTester tester) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorShape: RectangularSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
),
|
|
);
|
|
|
|
const value = 0.5;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Slider(value: value, label: '$value', onChanged: (double newValue) {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('PaddleSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorShape: PaddleSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
),
|
|
);
|
|
|
|
const value = 0.5;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Slider(value: value, label: '$value', onChanged: (double newValue) {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
});
|
|
|
|
testWidgets('DropSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
valueIndicatorShape: DropSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
),
|
|
);
|
|
|
|
const value = 0.5;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Slider(value: value, label: '$value', onChanged: (double newValue) {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'RectangularRangeSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor',
|
|
(WidgetTester tester) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
rangeValueIndicatorShape: RectangularRangeSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
),
|
|
);
|
|
|
|
var values = const RangeValues(0, 0.5);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: RangeSlider(
|
|
values: values,
|
|
labels: RangeLabels(values.start.toString(), values.end.toString()),
|
|
onChanged: (RangeValues val) {
|
|
values = val;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
|
|
await gesture.up();
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'RectangularRangeSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor on overlapping indicator',
|
|
(WidgetTester tester) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
rangeValueIndicatorShape: RectangularRangeSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
overlappingShapeStrokeColor: Color(0xff000003),
|
|
),
|
|
);
|
|
|
|
var values = const RangeValues(0.0, 0.0);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: RangeSlider(
|
|
values: values,
|
|
labels: RangeLabels(values.start.toString(), values.end.toString()),
|
|
onChanged: (RangeValues val) {
|
|
values = val;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor)
|
|
..path(color: theme.sliderTheme.overlappingShapeStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
|
|
await gesture.up();
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'PaddleRangeSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor',
|
|
(WidgetTester tester) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
rangeValueIndicatorShape: PaddleRangeSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
),
|
|
);
|
|
|
|
var values = const RangeValues(0, 0.5);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: RangeSlider(
|
|
values: values,
|
|
labels: RangeLabels(values.start.toString(), values.end.toString()),
|
|
onChanged: (RangeValues val) {
|
|
values = val;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
|
|
await gesture.up();
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'PaddleRangeSliderValueIndicatorShape supports SliderTheme.valueIndicatorStrokeColor on overlapping indicator',
|
|
(WidgetTester tester) async {
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
showValueIndicator: ShowValueIndicator.always,
|
|
rangeValueIndicatorShape: PaddleRangeSliderValueIndicatorShape(),
|
|
valueIndicatorColor: Color(0xff000001),
|
|
valueIndicatorStrokeColor: Color(0xff000002),
|
|
overlappingShapeStrokeColor: Color(0xff000003),
|
|
),
|
|
);
|
|
|
|
var values = const RangeValues(0, 0);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: RangeSlider(
|
|
values: values,
|
|
labels: RangeLabels(values.start.toString(), values.end.toString()),
|
|
onChanged: (RangeValues val) {
|
|
values = val;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
final Offset center = tester.getCenter(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
// Wait for value indicator animation to finish.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
valueIndicatorBox,
|
|
paints
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.colorScheme.shadow) // shadow
|
|
..path(color: theme.sliderTheme.valueIndicatorStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor)
|
|
..path(color: theme.sliderTheme.overlappingShapeStrokeColor)
|
|
..path(color: theme.sliderTheme.valueIndicatorColor),
|
|
);
|
|
|
|
await gesture.up();
|
|
},
|
|
);
|
|
|
|
group('RoundedRectSliderTrackShape', () {
|
|
testWidgets(
|
|
'Only draw active track if thumb center is higher than trackRect.left and track radius',
|
|
(WidgetTester tester) async {
|
|
const sliderTheme = SliderThemeData(trackShape: RoundedRectSliderTrackShape());
|
|
await tester.pumpWidget(_buildApp(sliderTheme));
|
|
|
|
MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(22.0, 298.0, 776.0, 302.0, const Radius.circular(2.0))),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.025));
|
|
|
|
material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(40.8, 298.0, 776.0, 302.0, const Radius.circular(2.0)))
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(24.0, 297.0, 44.8, 303.0, const Radius.circular(3.0))),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Only draw inactive track if thumb center is lower than trackRect.right and track radius',
|
|
(WidgetTester tester) async {
|
|
const sliderTheme = SliderThemeData(trackShape: RoundedRectSliderTrackShape());
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 1.0));
|
|
|
|
MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(24.0, 297.0, 778.0, 303.0, const Radius.circular(3.0))),
|
|
);
|
|
|
|
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.975));
|
|
|
|
material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(755.2, 298.0, 776.0, 302.0, const Radius.circular(2.0)))
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(24.0, 297.0, 759.2, 303.0, const Radius.circular(3.0))),
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
testWidgets('Track shape isRounded defaults', (WidgetTester tester) async {
|
|
expect(const RectangularSliderTrackShape().isRounded, isFalse);
|
|
expect(const RoundedRectSliderTrackShape().isRounded, isTrue);
|
|
expect(const RectangularRangeSliderTrackShape().isRounded, isFalse);
|
|
expect(const RoundedRectRangeSliderTrackShape().isRounded, isTrue);
|
|
});
|
|
|
|
testWidgets('SliderThemeData.padding can override the default Slider padding', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildSlider({EdgeInsetsGeometry? padding}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: SliderThemeData(padding: padding)),
|
|
home: Material(
|
|
child: Center(
|
|
child: IntrinsicHeight(child: Slider(value: 0.5, onChanged: (double value) {})),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox sliderRenderBox() {
|
|
return tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderSlider',
|
|
)
|
|
as RenderBox;
|
|
}
|
|
|
|
// Test Slider height and tracks spacing with zero padding.
|
|
await tester.pumpWidget(buildSlider(padding: EdgeInsets.zero));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The height equals to the default thumb height.
|
|
expect(sliderRenderBox().size, const Size(800, 20));
|
|
expect(
|
|
find.byType(Slider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(398.0, 8.0, 800.0, 12.0, const Radius.circular(2.0)))
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(0.0, 7.0, 402.0, 13.0, const Radius.circular(3.0))),
|
|
);
|
|
|
|
// Test Slider height and tracks spacing with directional padding.
|
|
const double startPadding = 100;
|
|
const double endPadding = 20;
|
|
await tester.pumpWidget(
|
|
buildSlider(
|
|
padding: const EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(sliderRenderBox().size, const Size(800 - startPadding - endPadding, 20));
|
|
expect(
|
|
find.byType(Slider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(338.0, 8.0, 680.0, 12.0, const Radius.circular(2.0)))
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(0.0, 7.0, 342.0, 13.0, const Radius.circular(3.0))),
|
|
);
|
|
|
|
// Test Slider height and tracks spacing with top and bottom padding.
|
|
const double topPadding = 100;
|
|
const double bottomPadding = 20;
|
|
const double trackHeight = 20;
|
|
await tester.pumpWidget(
|
|
buildSlider(
|
|
padding: const EdgeInsetsDirectional.only(top: topPadding, bottom: bottomPadding),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
tester.getSize(find.byType(Slider)),
|
|
const Size(800, topPadding + trackHeight + bottomPadding),
|
|
);
|
|
expect(sliderRenderBox().size, const Size(800, 20));
|
|
expect(
|
|
find.byType(Slider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(398.0, 8.0, 800.0, 12.0, const Radius.circular(2.0)))
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(0.0, 7.0, 402.0, 13.0, const Radius.circular(3.0))),
|
|
);
|
|
});
|
|
|
|
testWidgets('SliderThemeData.padding can override the default RangeSlider padding', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildRangeSlider({EdgeInsetsGeometry? padding}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: SliderThemeData(padding: padding)),
|
|
home: Material(
|
|
child: Center(
|
|
child: IntrinsicHeight(
|
|
child: RangeSlider(
|
|
values: const RangeValues(0, 1.0),
|
|
onChanged: (RangeValues values) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderBox sliderRenderBox() {
|
|
return tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderRangeSlider',
|
|
)
|
|
as RenderBox;
|
|
}
|
|
|
|
// Test RangeSlider height and tracks spacing with zero padding.
|
|
await tester.pumpWidget(buildRangeSlider(padding: EdgeInsets.zero));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The height equals to the default thumb height.
|
|
expect(sliderRenderBox().size, const Size(800, 20));
|
|
expect(
|
|
find.byType(RangeSlider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
10.0,
|
|
8.0,
|
|
10.0,
|
|
12.0,
|
|
topLeft: const Radius.circular(2.0),
|
|
bottomLeft: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
790.0,
|
|
8.0,
|
|
790.0,
|
|
12.0,
|
|
topRight: const Radius.circular(2.0),
|
|
bottomRight: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(8.0, 7.0, 792.0, 13.0, const Radius.circular(2.0))),
|
|
);
|
|
|
|
// Test RangeSlider height and tracks spacing with directional padding.
|
|
const double startPadding = 100;
|
|
const double endPadding = 20;
|
|
await tester.pumpWidget(
|
|
buildRangeSlider(
|
|
padding: const EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(sliderRenderBox().size, const Size(800 - startPadding - endPadding, 20));
|
|
expect(
|
|
find.byType(RangeSlider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
10.0,
|
|
8.0,
|
|
10.0,
|
|
12.0,
|
|
topLeft: const Radius.circular(2.0),
|
|
bottomLeft: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
670.0,
|
|
8.0,
|
|
670.0,
|
|
12.0,
|
|
topRight: const Radius.circular(2.0),
|
|
bottomRight: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(8.0, 7.0, 672.0, 13.0, const Radius.circular(2.0))),
|
|
);
|
|
|
|
// Test RangeSlider height and tracks spacing with top and bottom padding.
|
|
const double topPadding = 100;
|
|
const double bottomPadding = 20;
|
|
const double trackHeight = 20;
|
|
await tester.pumpWidget(
|
|
buildRangeSlider(
|
|
padding: const EdgeInsetsDirectional.only(top: topPadding, bottom: bottomPadding),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
tester.getSize(find.byType(RangeSlider)),
|
|
const Size(800, topPadding + trackHeight + bottomPadding),
|
|
);
|
|
expect(sliderRenderBox().size, const Size(800, 20));
|
|
expect(
|
|
find.byType(RangeSlider),
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
10.0,
|
|
8.0,
|
|
10.0,
|
|
12.0,
|
|
topLeft: const Radius.circular(2.0),
|
|
bottomLeft: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
790.0,
|
|
8.0,
|
|
790.0,
|
|
12.0,
|
|
topRight: const Radius.circular(2.0),
|
|
bottomRight: const Radius.circular(2.0),
|
|
),
|
|
)
|
|
// Active track.
|
|
..rrect(rrect: RRect.fromLTRBR(8.0, 7.0, 792.0, 13.0, const Radius.circular(2.0))),
|
|
);
|
|
});
|
|
|
|
testWidgets('Can customize Slider track gap when year2023 is false', (WidgetTester tester) async {
|
|
Widget buildSlider({double? trackGap}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: SliderThemeData(trackGap: trackGap)),
|
|
home: Material(
|
|
child: Center(child: Slider(year2023: false, value: 0.5, onChanged: (double value) {})),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSlider(trackGap: 0));
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Test default track shape.
|
|
const trackOuterCornerRadius = Radius.circular(8.0);
|
|
const trackInnerCornerRadius = Radius.circular(2.0);
|
|
expect(
|
|
material,
|
|
paints
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
292.0,
|
|
400.0,
|
|
308.0,
|
|
topLeft: trackOuterCornerRadius,
|
|
topRight: trackInnerCornerRadius,
|
|
bottomRight: trackInnerCornerRadius,
|
|
bottomLeft: trackOuterCornerRadius,
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
400.0,
|
|
292.0,
|
|
776.0,
|
|
308.0,
|
|
topLeft: trackInnerCornerRadius,
|
|
topRight: trackOuterCornerRadius,
|
|
bottomRight: trackOuterCornerRadius,
|
|
bottomLeft: trackInnerCornerRadius,
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(buildSlider(trackGap: 10));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
material,
|
|
paints
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
292.0,
|
|
390.0,
|
|
308.0,
|
|
topLeft: trackOuterCornerRadius,
|
|
topRight: trackInnerCornerRadius,
|
|
bottomRight: trackInnerCornerRadius,
|
|
bottomLeft: trackOuterCornerRadius,
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
410.0,
|
|
292.0,
|
|
776.0,
|
|
308.0,
|
|
topLeft: trackInnerCornerRadius,
|
|
topRight: trackOuterCornerRadius,
|
|
bottomRight: trackOuterCornerRadius,
|
|
bottomLeft: trackInnerCornerRadius,
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Can customize RangeSlider track gap when year2023 is false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildRangeSlider({double? trackGap}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: SliderThemeData(trackGap: trackGap)),
|
|
home: Material(
|
|
child: Center(
|
|
child: RangeSlider(
|
|
year2023: false,
|
|
values: const RangeValues(25, 75),
|
|
max: 100,
|
|
onChanged: (RangeValues value) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildRangeSlider(trackGap: 0));
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(RangeSlider)));
|
|
|
|
// Test default track shape.
|
|
const trackOuterCornerRadius = Radius.circular(8.0);
|
|
const trackInnerCornerRadius = Radius.circular(2.0);
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
292.0,
|
|
212.0,
|
|
308.0,
|
|
topLeft: trackOuterCornerRadius,
|
|
topRight: trackInnerCornerRadius,
|
|
bottomRight: trackInnerCornerRadius,
|
|
bottomLeft: trackOuterCornerRadius,
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
588.0,
|
|
292.0,
|
|
776.0,
|
|
308.0,
|
|
topLeft: trackInnerCornerRadius,
|
|
topRight: trackOuterCornerRadius,
|
|
bottomRight: trackOuterCornerRadius,
|
|
bottomLeft: trackInnerCornerRadius,
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(212.0, 292.0, 588.0, 308.0, trackInnerCornerRadius)),
|
|
);
|
|
|
|
await tester.pumpWidget(buildRangeSlider(trackGap: 10));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
material,
|
|
paints
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
292.0,
|
|
202.0,
|
|
308.0,
|
|
topLeft: trackOuterCornerRadius,
|
|
topRight: trackInnerCornerRadius,
|
|
bottomRight: trackInnerCornerRadius,
|
|
bottomLeft: trackOuterCornerRadius,
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
598.0,
|
|
292.0,
|
|
776.0,
|
|
308.0,
|
|
topLeft: trackInnerCornerRadius,
|
|
topRight: trackOuterCornerRadius,
|
|
bottomRight: trackOuterCornerRadius,
|
|
bottomLeft: trackInnerCornerRadius,
|
|
),
|
|
)
|
|
// Inactive track.
|
|
..rrect(rrect: RRect.fromLTRBR(222.0, 292.0, 578.0, 308.0, trackInnerCornerRadius)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Can customize Slider thumb size when year2023 is false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildSlider({WidgetStateProperty<Size?>? thumbSize}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: SliderThemeData(thumbSize: thumbSize)),
|
|
home: Material(
|
|
child: Center(child: Slider(year2023: false, value: 0.5, onChanged: (double value) {})),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
buildSlider(thumbSize: const WidgetStatePropertyAll<Size>(Size(20, 20))),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle()
|
|
..rrect(rrect: RRect.fromLTRBR(390.0, 290.0, 410.0, 310.0, const Radius.circular(10.0))),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildSlider(
|
|
thumbSize: const WidgetStateProperty<Size?>.fromMap(<WidgetStatesConstraint, Size>{
|
|
WidgetState.pressed: Size(20, 20),
|
|
WidgetState.any: Size(10, 10),
|
|
}),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle()
|
|
..rrect(rrect: RRect.fromLTRBR(395.0, 295.0, 405.0, 305.0, const Radius.circular(5.0))),
|
|
);
|
|
|
|
final Offset center = tester.getCenter(find.byType(Slider));
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle()
|
|
..rrect(rrect: RRect.fromLTRBR(390.0, 295.0, 410.0, 305.0, const Radius.circular(5.0))),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Can customize RangeSlider thumbs size when year2023 is false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildRangeSlider({WidgetStateProperty<Size?>? thumbSize}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(sliderTheme: SliderThemeData(thumbSize: thumbSize)),
|
|
home: Material(
|
|
child: Center(
|
|
child: RangeSlider(
|
|
year2023: false,
|
|
values: const RangeValues(25, 75),
|
|
max: 100,
|
|
onChanged: (RangeValues value) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
buildRangeSlider(thumbSize: const WidgetStatePropertyAll<Size>(Size(20, 20))),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(RangeSlider)));
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle()
|
|
..rrect(rrect: RRect.fromLTRBR(202.0, 290.0, 222.0, 310.0, const Radius.circular(10.0)))
|
|
..rrect(rrect: RRect.fromLTRBR(578.0, 290.0, 598.0, 310.0, const Radius.circular(10.0))),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildRangeSlider(
|
|
thumbSize: const WidgetStateProperty<Size?>.fromMap(<WidgetStatesConstraint, Size>{
|
|
WidgetState.pressed: Size(20, 20),
|
|
WidgetState.any: Size(10, 10),
|
|
}),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle()
|
|
..rrect(rrect: RRect.fromLTRBR(207.0, 295.0, 217.0, 305.0, const Radius.circular(5.0)))
|
|
..rrect(rrect: RRect.fromLTRBR(583.0, 295.0, 593.0, 305.0, const Radius.circular(5.0))),
|
|
);
|
|
|
|
final Offset topRight = tester.getTopRight(find.byType(RangeSlider));
|
|
final TestGesture gesture = await tester.startGesture(topRight);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle()
|
|
..rrect(rrect: RRect.fromLTRBR(207.0, 295.0, 217.0, 305.0, const Radius.circular(5.0)))
|
|
..rrect(rrect: RRect.fromLTRBR(583.0, 295.0, 593.0, 305.0, const Radius.circular(5.0))),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Opt into 2024 Slider appearance with SliderThemeData.year2023', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(sliderTheme: const SliderThemeData(year2023: false));
|
|
final ColorScheme colorScheme = theme.colorScheme;
|
|
final Color activeTrackColor = colorScheme.primary;
|
|
final Color inactiveTrackColor = colorScheme.secondaryContainer;
|
|
const value = 0.45;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Slider(value: value, onChanged: (double value) {}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Test default track shape.
|
|
const trackOuterCornerRadius = Radius.circular(8.0);
|
|
const trackInnerCornerRadius = Radius.circular(2.0);
|
|
expect(
|
|
material,
|
|
paints
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
24.0,
|
|
292.0,
|
|
356.4,
|
|
308.0,
|
|
topLeft: trackOuterCornerRadius,
|
|
topRight: trackInnerCornerRadius,
|
|
bottomRight: trackInnerCornerRadius,
|
|
bottomLeft: trackOuterCornerRadius,
|
|
),
|
|
color: activeTrackColor,
|
|
)
|
|
// Inctive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBAndCorners(
|
|
368.4,
|
|
292.0,
|
|
776.0,
|
|
308.0,
|
|
topLeft: trackInnerCornerRadius,
|
|
topRight: trackOuterCornerRadius,
|
|
bottomRight: trackOuterCornerRadius,
|
|
bottomLeft: trackInnerCornerRadius,
|
|
),
|
|
color: inactiveTrackColor,
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Slider.year2023 overrides SliderThemeData.year2023', (WidgetTester tester) async {
|
|
final theme = ThemeData(sliderTheme: const SliderThemeData(year2023: false));
|
|
final ColorScheme colorScheme = theme.colorScheme;
|
|
final Color activeTrackColor = colorScheme.primary;
|
|
final Color inactiveTrackColor = colorScheme.surfaceContainerHighest;
|
|
const value = 0.45;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Theme(
|
|
data: theme,
|
|
child: Slider(year2023: true, value: value, onChanged: (double value) {}),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
// Test default track shape.
|
|
const activeTrackCornerRadius = Radius.circular(3.0);
|
|
const inactiveTrackCornerRadius = Radius.circular(2.0);
|
|
expect(
|
|
material,
|
|
paints
|
|
// Active track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(360.4, 298.0, 776.0, 302.0, inactiveTrackCornerRadius),
|
|
color: inactiveTrackColor,
|
|
)
|
|
// Inctive track.
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 297.0, 364.4, 303.0, activeTrackCornerRadius),
|
|
color: activeTrackColor,
|
|
),
|
|
);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/161210
|
|
testWidgets(
|
|
'Slider with transparent track colors and custom track height can reach extreme ends',
|
|
(WidgetTester tester) async {
|
|
const sliderPadding = 24.0;
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
trackHeight: 100,
|
|
activeTrackColor: Colors.transparent,
|
|
inactiveTrackColor: Colors.transparent,
|
|
),
|
|
);
|
|
|
|
Widget buildSlider({required double value}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: SizedBox(
|
|
width: 300,
|
|
child: Slider(value: value, onChanged: (double value) {}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSlider(value: 0));
|
|
|
|
MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
|
|
|
|
expect(
|
|
material,
|
|
paints..circle(x: sliderPadding, y: 300.0, color: theme.colorScheme.primary),
|
|
);
|
|
|
|
await tester.pumpWidget(buildSlider(value: 1));
|
|
|
|
material = Material.of(tester.element(find.byType(Slider)));
|
|
expect(
|
|
material,
|
|
paints..circle(x: 800.0 - sliderPadding, y: 300.0, color: theme.colorScheme.primary),
|
|
);
|
|
},
|
|
);
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/161210
|
|
testWidgets(
|
|
'RangeSlider with transparent track colors and custom track height can reach extreme ends',
|
|
(WidgetTester tester) async {
|
|
const sliderPadding = 24.0;
|
|
final theme = ThemeData(
|
|
sliderTheme: const SliderThemeData(
|
|
trackHeight: 100,
|
|
activeTrackColor: Colors.transparent,
|
|
inactiveTrackColor: Colors.transparent,
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: SizedBox(
|
|
width: 300,
|
|
child: RangeSlider(
|
|
values: const RangeValues(0, 1),
|
|
onChanged: (RangeValues values) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final MaterialInkController material = Material.of(tester.element(find.byType(RangeSlider)));
|
|
|
|
expect(
|
|
material,
|
|
paints
|
|
..circle(x: sliderPadding, y: 300.0, color: theme.colorScheme.primary)
|
|
..circle(x: 800.0 - sliderPadding, y: 300.0, color: theme.colorScheme.primary),
|
|
);
|
|
},
|
|
);
|
|
|
|
group('Material 2', () {
|
|
// These tests are only relevant for Material 2. Once Material 2
|
|
// support is deprecated and the APIs are removed, these tests
|
|
// can be deleted.
|
|
|
|
testWidgets('Slider defaults', (WidgetTester tester) async {
|
|
debugDisableShadows = false;
|
|
final theme = ThemeData(useMaterial3: false);
|
|
const trackHeight = 4.0;
|
|
final ColorScheme colorScheme = theme.colorScheme;
|
|
final 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 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 {
|
|
var value = 0.45;
|
|
Widget buildApp({int? divisions, bool enabled = true}) {
|
|
final ValueChanged<double>? onChanged = !enabled
|
|
? null
|
|
: (double d) {
|
|
value = d;
|
|
};
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: 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.circular(trackHeight / 2);
|
|
const activatedRadius = Radius.circular((trackHeight + 2) / 2);
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(360.4, 298.0, 776.0, 302.0, radius),
|
|
color: inactiveTrackColor,
|
|
)
|
|
..rrect(
|
|
rrect: RRect.fromLTRBR(24.0, 297.0, 364.4, 303.0, activatedRadius),
|
|
color: activeTrackColor,
|
|
),
|
|
);
|
|
|
|
// Test default colors for enabled slider.
|
|
expect(
|
|
material,
|
|
paints
|
|
..rrect(color: inactiveTrackColor)
|
|
..rrect(color: activeTrackColor)
|
|
..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: inactiveTrackColor)
|
|
..rrect(color: activeTrackColor)
|
|
..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: disabledInactiveTrackColor)
|
|
..rrect(color: disabledActiveTrackColor)
|
|
..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;
|
|
}
|
|
});
|
|
|
|
testWidgets('Default value indicator color', (WidgetTester tester) async {
|
|
debugDisableShadows = false;
|
|
try {
|
|
final theme = ThemeData(useMaterial3: false, platform: TargetPlatform.android);
|
|
Widget buildApp(
|
|
String value, {
|
|
double sliderValue = 0.5,
|
|
TextScaler textScaler = TextScaler.noScaling,
|
|
}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Material(
|
|
child: Row(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: Slider(
|
|
value: sliderValue,
|
|
label: value,
|
|
divisions: 3,
|
|
onChanged: (double d) {},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp('1'));
|
|
|
|
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
|
|
|
|
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(
|
|
valueIndicatorBox,
|
|
paints
|
|
..rrect(color: const Color(0xfffafafa))
|
|
..rrect(color: const Color(0x3d2196f3))
|
|
..rrect(color: const Color(0xff2196f3))
|
|
// Test that the value indicator text is painted with the correct color.
|
|
..path(color: const Color(0xf55f5f5f)),
|
|
);
|
|
|
|
// Finish gesture to release resources.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
} finally {
|
|
debugDisableShadows = true;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
class RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight
|
|
extends RoundedRectSliderTrackShape {
|
|
const RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight({
|
|
required this.additionalActiveTrackHeight,
|
|
});
|
|
final double additionalActiveTrackHeight;
|
|
@override
|
|
void paint(
|
|
PaintingContext context,
|
|
Offset offset, {
|
|
required RenderBox parentBox,
|
|
required SliderThemeData sliderTheme,
|
|
required Animation<double> enableAnimation,
|
|
required TextDirection textDirection,
|
|
required Offset thumbCenter,
|
|
Offset? secondaryOffset,
|
|
bool isDiscrete = false,
|
|
bool isEnabled = false,
|
|
double additionalActiveTrackHeight = 2.0,
|
|
}) {
|
|
super.paint(
|
|
context,
|
|
offset,
|
|
parentBox: parentBox,
|
|
sliderTheme: sliderTheme,
|
|
enableAnimation: enableAnimation,
|
|
textDirection: textDirection,
|
|
thumbCenter: thumbCenter,
|
|
secondaryOffset: secondaryOffset,
|
|
additionalActiveTrackHeight: this.additionalActiveTrackHeight,
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget _buildApp(
|
|
SliderThemeData sliderTheme, {
|
|
double value = 0.0,
|
|
double? secondaryTrackValue,
|
|
bool enabled = true,
|
|
int? divisions,
|
|
FocusNode? focusNode,
|
|
}) {
|
|
final ValueChanged<double>? onChanged = enabled ? (double d) => value = d : null;
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: SliderTheme(
|
|
data: sliderTheme,
|
|
child: Slider(
|
|
value: value,
|
|
secondaryTrackValue: secondaryTrackValue,
|
|
label: '$value',
|
|
onChanged: onChanged,
|
|
divisions: divisions,
|
|
focusNode: focusNode,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRangeApp(
|
|
SliderThemeData sliderTheme, {
|
|
RangeValues values = const RangeValues(0, 0),
|
|
bool enabled = true,
|
|
int? divisions,
|
|
}) {
|
|
final ValueChanged<RangeValues>? onChanged = enabled ? (RangeValues d) => values = d : null;
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: SliderTheme(
|
|
data: sliderTheme,
|
|
child: RangeSlider(
|
|
values: values,
|
|
labels: RangeLabels(values.start.toString(), values.end.toString()),
|
|
onChanged: onChanged,
|
|
divisions: divisions,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|