Unverified Commit df8d8194 authored by Mubarak Bakarman's avatar Mubarak Bakarman Committed by GitHub

Customize color and thickness of connected lines in Stepper.dart (#122485)

parent 53f31c7c
...@@ -211,6 +211,8 @@ class Stepper extends StatefulWidget { ...@@ -211,6 +211,8 @@ class Stepper extends StatefulWidget {
this.controlsBuilder, this.controlsBuilder,
this.elevation, this.elevation,
this.margin, this.margin,
this.connectorColor,
this.connectorThickness,
this.stepIconBuilder, this.stepIconBuilder,
}) : assert(0 <= currentStep && currentStep < steps.length); }) : assert(0 <= currentStep && currentStep < steps.length);
...@@ -311,6 +313,19 @@ class Stepper extends StatefulWidget { ...@@ -311,6 +313,19 @@ class Stepper extends StatefulWidget {
/// Custom margin on vertical stepper. /// Custom margin on vertical stepper.
final EdgeInsetsGeometry? margin; final EdgeInsetsGeometry? margin;
/// Customize connected lines colors.
///
/// Resolves in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.disabled].
///
/// If not set then the widget will use default colors, primary for selected state
/// and grey.shade400 for disabled state.
final MaterialStateProperty<Color>? connectorColor;
/// The thickness of the connecting lines.
final double? connectorThickness;
/// Callback for creating custom icons for the [steps]. /// Callback for creating custom icons for the [steps].
/// ///
/// When overriding icon for [StepState.error], please return /// When overriding icon for [StepState.error], please return
...@@ -375,11 +390,23 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -375,11 +390,23 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
return false; return false;
} }
Widget _buildLine(bool visible) { Color _connectorColor(bool isActive) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Set<MaterialState> states = <MaterialState>{
if (isActive) MaterialState.selected else MaterialState.disabled,
};
final Color? resolvedConnectorColor = widget.connectorColor?.resolve(states);
if (resolvedConnectorColor != null) {
return resolvedConnectorColor;
}
return isActive ? colorScheme.primary : Colors.grey.shade400;
}
Widget _buildLine(bool visible, bool isActive) {
return Container( return Container(
width: visible ? 1.0 : 0.0, width: visible ? widget.connectorThickness ?? 1.0 : 0.0,
height: 16.0, height: 16.0,
color: Colors.grey.shade400, color: _connectorColor(isActive),
); );
} }
...@@ -415,11 +442,19 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -415,11 +442,19 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
} }
Color _circleColor(int index) { Color _circleColor(int index) {
final bool isActive = widget.steps[index].isActive;
final ColorScheme colorScheme = Theme.of(context).colorScheme; final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Set<MaterialState> states = <MaterialState>{
if (isActive) MaterialState.selected else MaterialState.disabled,
};
final Color? resolvedConnectorColor = widget.connectorColor?.resolve(states);
if (resolvedConnectorColor != null) {
return resolvedConnectorColor;
}
if (!_isDark()) { if (!_isDark()) {
return widget.steps[index].isActive ? colorScheme.primary : colorScheme.onSurface.withOpacity(0.38); return isActive ? colorScheme.primary : colorScheme.onSurface.withOpacity(0.38);
} else { } else {
return widget.steps[index].isActive ? colorScheme.secondary : colorScheme.background; return isActive ? colorScheme.secondary : colorScheme.background;
} }
} }
...@@ -659,6 +694,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -659,6 +694,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
} }
Widget _buildVerticalHeader(int index) { Widget _buildVerticalHeader(int index) {
final bool isActive = widget.steps[index].isActive;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0), margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row( child: Row(
...@@ -667,9 +703,9 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -667,9 +703,9 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
children: <Widget>[ children: <Widget>[
// Line parts are always added in order for the ink splash to // Line parts are always added in order for the ink splash to
// flood the tips of the connector lines. // flood the tips of the connector lines.
_buildLine(!_isFirst(index)), _buildLine(!_isFirst(index), isActive),
_buildIcon(index), _buildIcon(index),
_buildLine(!_isLast(index)), _buildLine(!_isLast(index), isActive),
], ],
), ),
Expanded( Expanded(
...@@ -694,9 +730,9 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -694,9 +730,9 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
width: 24.0, width: 24.0,
child: Center( child: Center(
child: SizedBox( child: SizedBox(
width: _isLast(index) ? 0.0 : 1.0, width: widget.connectorThickness ?? 1.0,
child: Container( child: Container(
color: Colors.grey.shade400, color: _connectorColor(widget.steps[index].isActive),
), ),
), ),
), ),
...@@ -789,9 +825,10 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ...@@ -789,9 +825,10 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
if (!_isLast(i)) if (!_isLast(i))
Expanded( Expanded(
child: Container( child: Container(
key: Key('line$i'),
margin: const EdgeInsets.symmetric(horizontal: 8.0), margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0, height: widget.connectorThickness ?? 1.0,
color: Colors.grey.shade400, color: _connectorColor(widget.steps[i+1].isActive),
), ),
), ),
], ],
......
...@@ -1260,6 +1260,75 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async ...@@ -1260,6 +1260,75 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async
expect(bodyMediumStyle, nextLabelTextWidget.style); expect(bodyMediumStyle, nextLabelTextWidget.style);
}); });
testWidgets('Stepper Connector Style', (WidgetTester tester) async {
const Color selectedColor = Colors.black;
const Color disabledColor = Colors.white;
int index = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Stepper(
type: StepperType.horizontal,
connectorColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) =>
states.contains(MaterialState.selected)
? selectedColor
: disabledColor),
onStepTapped: (int i) => setState(() => index = i),
currentStep: index,
steps: <Step>[
Step(
isActive: index >= 0,
title: const Text('step1'),
content: const Text('step1 content'),
),
Step(
isActive: index >= 1,
title: const Text('step2'),
content: const Text('step2 content'),
),
],
);
},
),
),
),
)
);
Color? circleColor(String circleText) => (tester.widget<AnimatedContainer>(
find.widgetWithText(AnimatedContainer, circleText),
).decoration as BoxDecoration?)?.color;
Color? lineColor(String keyStep) => tester.widget<Container>(find.byKey(Key(keyStep))).color;
// Step 1
// check if I'm in step 1
expect(find.text('step1 content'), findsOneWidget);
expect(find.text('step2 content'), findsNothing);
expect(circleColor('1'), selectedColor);
expect(circleColor('2'), disabledColor);
// in two steps case there will be single line
expect(lineColor('line0'), disabledColor);
// now hitting step two
await tester.tap(find.text('step2'));
await tester.pumpAndSettle();
// check if I'm in step 1
expect(find.text('step1 content'), findsNothing);
expect(find.text('step2 content'), findsOneWidget);
expect(circleColor('1'), selectedColor);
expect(circleColor('2'), selectedColor);
expect(lineColor('line0'), selectedColor);
});
testWidgets('Stepper stepIconBuilder test', (WidgetTester tester) async { testWidgets('Stepper stepIconBuilder test', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment