diff --git a/packages/flutter/lib/src/material/stepper.dart b/packages/flutter/lib/src/material/stepper.dart index 1ee3d63231..d47f7a26e9 100644 --- a/packages/flutter/lib/src/material/stepper.dart +++ b/packages/flutter/lib/src/material/stepper.dart @@ -6,14 +6,16 @@ import 'package:flutter/widgets.dart'; -import 'button_theme.dart'; +import 'button_style.dart'; +import 'color_scheme.dart'; import 'colors.dart'; import 'debug.dart'; -import 'flat_button.dart'; import 'icons.dart'; import 'ink_well.dart'; import 'material.dart'; import 'material_localizations.dart'; +import 'material_state.dart'; +import 'text_button.dart'; import 'text_theme.dart'; import 'theme.dart'; @@ -191,7 +193,7 @@ class Stepper extends StatefulWidget { /// /// If null, the default controls from the current theme will be used. /// - /// This callback which takes in a context and two functions,[onStepContinue] + /// This callback which takes in a context and two functions: [onStepContinue] /// and [onStepCancel]. These can be used to control the stepper. /// /// {@tool dartpad --template=stateless_widget_scaffold} @@ -201,14 +203,14 @@ class Stepper extends StatefulWidget { /// Widget build(BuildContext context) { /// return Stepper( /// controlsBuilder: - /// (BuildContext context, {VoidCallback onStepContinue, VoidCallback onStepCancel}) { + /// (BuildContext context, { VoidCallback onStepContinue, VoidCallback onStepCancel }) { /// return Row( /// children: [ - /// FlatButton( + /// TextButton( /// onPressed: onStepContinue, /// child: const Text('NEXT'), /// ), - /// FlatButton( + /// TextButton( /// onPressed: onStepCancel, /// child: const Text('CANCEL'), /// ), @@ -407,27 +409,44 @@ class _StepperState extends State with TickerProviderStateMixin { assert(cancelColor != null); final ThemeData themeData = Theme.of(context); + final ColorScheme colorScheme = themeData.colorScheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); + const OutlinedBorder buttonShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2))); + const EdgeInsets buttonPadding = EdgeInsets.symmetric(horizontal: 16.0); + return Container( margin: const EdgeInsets.only(top: 16.0), child: ConstrainedBox( constraints: const BoxConstraints.tightFor(height: 48.0), child: Row( + // The Material spec no longer includes a Stepper widget. The continue + // and cancel button styles have been configured to match the original + // version of this widget. children: [ - FlatButton( + TextButton( onPressed: widget.onStepContinue, - color: _isDark() ? themeData.backgroundColor : themeData.primaryColor, - textColor: Colors.white, - textTheme: ButtonTextTheme.normal, + style: ButtonStyle( + foregroundColor: MaterialStateProperty.resolveWith((Set states) { + return states.contains(MaterialState.disabled) ? null : (_isDark() ? colorScheme.onSurface : colorScheme.onPrimary); + }), + backgroundColor: MaterialStateProperty.resolveWith((Set states) { + return _isDark() || states.contains(MaterialState.disabled) ? null : colorScheme.primary; + }), + padding: MaterialStateProperty.all(buttonPadding), + shape: MaterialStateProperty.all(buttonShape), + ), child: Text(localizations.continueButtonLabel), ), Container( margin: const EdgeInsetsDirectional.only(start: 8.0), - child: FlatButton( + child: TextButton( onPressed: widget.onStepCancel, - textColor: cancelColor, - textTheme: ButtonTextTheme.normal, + style: TextButton.styleFrom( + primary: cancelColor, + padding: buttonPadding, + shape: buttonShape, + ), child: Text(localizations.cancelButtonLabel), ), ), diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart index fe3ee2a7ee..ca1bb412d3 100644 --- a/packages/flutter/test/material/stepper_test.dart +++ b/packages/flutter/test/material/stepper_test.dart @@ -390,19 +390,14 @@ void main() { constraints: const BoxConstraints.tightFor(height: 48.0), child: Row( children: [ - FlatButton( + TextButton( onPressed: onStepContinue, - color: Colors.blue, - textColor: Colors.white, - textTheme: ButtonTextTheme.normal, child: const Text('Let us continue!'), ), Container( margin: const EdgeInsetsDirectional.only(start: 8.0), - child: FlatButton( + child: TextButton( onPressed: onStepCancel, - textColor: Colors.red, - textTheme: ButtonTextTheme.normal, child: const Text('Cancel This!'), ), ), @@ -716,4 +711,110 @@ void main() { expect(tester.takeException(), isNull); }); + + testWidgets('Stepper enabled button styles', (WidgetTester tester) async { + Widget buildFrame(ThemeData theme) { + return MaterialApp( + theme: theme, + home: Material( + child: Stepper( + type: StepperType.horizontal, + onStepCancel: () { }, + onStepContinue: () { }, + steps: const [ + Step( + title: Text('step1'), + content: SizedBox(width: 100, height: 100), + ), + ], + ), + ), + ); + } + + Material buttonMaterial(String label) { + return tester.widget( + find.descendant(of: find.widgetWithText(TextButton, label), matching: find.byType(Material)) + ); + } + + // The checks that follow verify that the layout and appearance of + // the default enabled Stepper buttons have not changed even + // though the FlatButtons have been replaced by TextButtons. + + const OutlinedBorder buttonShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2))); + const Rect continueButtonRect = Rect.fromLTRB(24.0, 212.0, 168.0, 260.0); + const Rect cancelButtonRect = Rect.fromLTRB(176.0, 212.0, 292.0, 260.0); + + await tester.pumpWidget(buildFrame(ThemeData.light())); + + expect(buttonMaterial('CONTINUE').color.value, 0xff2196f3); + expect(buttonMaterial('CONTINUE').textStyle.color.value, 0xffffffff); + expect(buttonMaterial('CONTINUE').shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, 'CONTINUE')), continueButtonRect); + + expect(buttonMaterial('CANCEL').color.value, 0); + expect(buttonMaterial('CANCEL').textStyle.color.value, 0x8a000000); + expect(buttonMaterial('CANCEL').shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, 'CANCEL')), cancelButtonRect); + + await tester.pumpWidget(buildFrame(ThemeData.dark())); + await tester.pumpAndSettle(); // Complete the theme animation. + + expect(buttonMaterial('CONTINUE').color.value, 0); + expect(buttonMaterial('CONTINUE').textStyle.color.value, 0xffffffff); + expect(buttonMaterial('CONTINUE').shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, 'CONTINUE')), continueButtonRect); + + expect(buttonMaterial('CANCEL').color.value, 0); + expect(buttonMaterial('CANCEL').textStyle.color.value, 0xb3ffffff); + expect(buttonMaterial('CANCEL').shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, 'CANCEL')), cancelButtonRect); + }); + + testWidgets('Stepper disabled button styles', (WidgetTester tester) async { + Widget buildFrame(ThemeData theme) { + return MaterialApp( + theme: theme, + home: Material( + child: Stepper( + type: StepperType.horizontal, + steps: const [ + Step( + title: Text('step1'), + content: SizedBox(width: 100, height: 100), + ), + ], + ), + ), + ); + } + + Material buttonMaterial(String label) { + return tester.widget( + find.descendant(of: find.widgetWithText(TextButton, label), matching: find.byType(Material)) + ); + } + + // The checks that follow verify that the appearance of the + // default disabled Stepper buttons have not changed even though + // the FlatButtons have been replaced by TextButtons. + + await tester.pumpWidget(buildFrame(ThemeData.light())); + + expect(buttonMaterial('CONTINUE').color.value, 0); + expect(buttonMaterial('CONTINUE').textStyle.color.value, 0x61000000); + + expect(buttonMaterial('CANCEL').color.value, 0); + expect(buttonMaterial('CANCEL').textStyle.color.value, 0x61000000); + + await tester.pumpWidget(buildFrame(ThemeData.dark())); + await tester.pumpAndSettle(); // Complete the theme animation. + + expect(buttonMaterial('CONTINUE').color.value, 0); + expect(buttonMaterial('CONTINUE').textStyle.color.value, 0x61ffffff); + + expect(buttonMaterial('CANCEL').color.value, 0); + expect(buttonMaterial('CANCEL').textStyle.color.value, 0x61ffffff); + }); }