Update ToggleButtons constraints default and add new constraints parameter (#39857)

* Add constraints property, updated default constraints for ToggleButtons to 48x48

* Add kMinInteractiveDimension constant to ToggleButtons
diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart
index 66d29e2..9671901 100644
--- a/packages/flutter/lib/src/material/toggle_buttons.dart
+++ b/packages/flutter/lib/src/material/toggle_buttons.dart
@@ -9,6 +9,7 @@
 import 'package:flutter/widgets.dart';
 
 import 'button.dart';
+import 'constants.dart';
 import 'debug.dart';
 import 'theme.dart';
 import 'theme_data.dart';
@@ -166,6 +167,7 @@
     @required this.isSelected,
     this.onPressed,
     this.textStyle,
+    this.constraints,
     this.color,
     this.selectedColor,
     this.disabledColor,
@@ -223,6 +225,14 @@
   /// are active, selected, or disabled.
   final TextStyle textStyle;
 
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  ///
+  /// If this property is null, then
+  /// BoxConstraints(minWidth: 48.0, minHeight: 48.0) is be used.
+  final BoxConstraints constraints;
+
   /// The color for descendant [Text] and [Icon] widgets if the button is
   /// enabled and not selected.
   ///
@@ -578,6 +588,7 @@
           return _ToggleButton(
             selected: isSelected[index],
             textStyle: textStyle,
+            constraints: constraints,
             color: color,
             selectedColor: selectedColor,
             disabledColor: disabledColor,
@@ -645,6 +656,7 @@
     Key key,
     this.selected = false,
     this.textStyle,
+    this.constraints,
     this.color,
     this.selectedColor,
     this.disabledColor,
@@ -671,6 +683,11 @@
   /// The [TextStyle] to apply to any text that appears in this button.
   final TextStyle textStyle;
 
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  final BoxConstraints constraints;
+
   /// The color for [Text] and [Icon] widgets if the button is enabled.
   ///
   /// If [selected] is false and [onPressed] is not null, this color will be used.
@@ -784,6 +801,7 @@
     }
 
     final TextStyle currentTextStyle = textStyle ?? toggleButtonsTheme.textStyle ?? theme.textTheme.body1;
+    final BoxConstraints currentConstraints = constraints ?? toggleButtonsTheme.constraints ?? const BoxConstraints(minWidth: kMinInteractiveDimension, minHeight: kMinInteractiveDimension);
 
     final Widget result = ClipRRect(
       borderRadius: clipRadius,
@@ -791,6 +809,7 @@
         textStyle: currentTextStyle.copyWith(
           color: currentColor,
         ),
+        constraints: currentConstraints,
         elevation: 0.0,
         highlightElevation: 0.0,
         fillColor: currentFillColor,
diff --git a/packages/flutter/lib/src/material/toggle_buttons_theme.dart b/packages/flutter/lib/src/material/toggle_buttons_theme.dart
index d80b6ed..865667f 100644
--- a/packages/flutter/lib/src/material/toggle_buttons_theme.dart
+++ b/packages/flutter/lib/src/material/toggle_buttons_theme.dart
@@ -29,6 +29,7 @@
   /// [ToggleButtons].
   const ToggleButtonsThemeData({
     this.textStyle,
+    this.constraints,
     this.color,
     this.selectedColor,
     this.disabledColor,
@@ -51,6 +52,11 @@
   /// are active, selected, or disabled.
   final TextStyle textStyle;
 
+  /// Defines the button's size.
+  ///
+  /// Typically used to constrain the button's minimum size.
+  final BoxConstraints constraints;
+
   /// The color for descendant [Text] and [Icon] widgets if the toggle button
   /// is enabled.
   final Color color;
@@ -104,6 +110,7 @@
   /// new values.
   ToggleButtonsThemeData copyWith({
     TextStyle textStyle,
+    BoxConstraints constraints,
     Color color,
     Color selectedColor,
     Color disabledColor,
@@ -120,6 +127,7 @@
   }) {
     return ToggleButtonsThemeData(
       textStyle: textStyle ?? this.textStyle,
+      constraints: constraints ?? this.constraints,
       color: color ?? this.color,
       selectedColor: selectedColor ?? this.selectedColor,
       disabledColor: disabledColor ?? this.disabledColor,
@@ -143,6 +151,7 @@
       return null;
     return ToggleButtonsThemeData(
       textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
+      constraints: BoxConstraints.lerp(a?.constraints, b?.constraints, t),
       color: Color.lerp(a?.color, b?.color, t),
       selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
       disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t),
@@ -163,6 +172,7 @@
   int get hashCode {
     return hashValues(
       textStyle,
+      constraints,
       color,
       selectedColor,
       disabledColor,
@@ -187,6 +197,7 @@
       return false;
     final ToggleButtonsThemeData typedOther = other;
     return typedOther.textStyle == textStyle
+        && typedOther.constraints == constraints
         && typedOther.color == color
         && typedOther.selectedColor == selectedColor
         && typedOther.disabledColor == disabledColor
@@ -206,6 +217,7 @@
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     textStyle?.debugFillProperties(properties, prefix: 'textStyle.');
+    properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
     properties.add(ColorProperty('color', color, defaultValue: null));
     properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
     properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
diff --git a/packages/flutter/test/material/toggle_buttons_test.dart b/packages/flutter/test/material/toggle_buttons_test.dart
index f7eafa8..14a8fb4 100644
--- a/packages/flutter/test/material/toggle_buttons_test.dart
+++ b/packages/flutter/test/material/toggle_buttons_test.dart
@@ -309,6 +309,98 @@
     expect(textStyle.color, isNot(Colors.orange));
   });
 
+  testWidgets('Default BoxConstraints', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: ToggleButtons(
+            isSelected: const <bool>[false, false, false],
+            onPressed: (int index) {},
+            children: const <Widget>[
+              Icon(Icons.check),
+              Icon(Icons.access_alarm),
+              Icon(Icons.cake),
+            ],
+          ),
+        ),
+      ),
+    );
+
+    final Rect firstRect = tester.getRect(find.byType(RawMaterialButton).at(0));
+    expect(firstRect.width, 48.0);
+    expect(firstRect.height, 48.0);
+    final Rect secondRect = tester.getRect(find.byType(RawMaterialButton).at(1));
+    expect(secondRect.width, 48.0);
+    expect(secondRect.height, 48.0);
+    final Rect thirdRect = tester.getRect(find.byType(RawMaterialButton).at(2));
+    expect(thirdRect.width, 48.0);
+    expect(thirdRect.height, 48.0);
+  });
+
+  testWidgets('Custom BoxConstraints', (WidgetTester tester) async {
+    // Test for minimum constraints
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: ToggleButtons(
+            constraints: const BoxConstraints(
+              minWidth: 50.0,
+              minHeight: 60.0,
+            ),
+            isSelected: const <bool>[false, false, false],
+            onPressed: (int index) {},
+            children: const <Widget>[
+              Icon(Icons.check),
+              Icon(Icons.access_alarm),
+              Icon(Icons.cake),
+            ],
+          ),
+        ),
+      ),
+    );
+
+    Rect firstRect = tester.getRect(find.byType(RawMaterialButton).at(0));
+    expect(firstRect.width, 50.0);
+    expect(firstRect.height, 60.0);
+    Rect secondRect = tester.getRect(find.byType(RawMaterialButton).at(1));
+    expect(secondRect.width, 50.0);
+    expect(secondRect.height, 60.0);
+    Rect thirdRect = tester.getRect(find.byType(RawMaterialButton).at(2));
+    expect(thirdRect.width, 50.0);
+    expect(thirdRect.height, 60.0);
+
+    // Test for maximum constraints
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: ToggleButtons(
+            constraints: const BoxConstraints(
+              maxWidth: 20.0,
+              maxHeight: 10.0,
+            ),
+            isSelected: const <bool>[false, false, false],
+            onPressed: (int index) {},
+            children: const <Widget>[
+              Icon(Icons.check),
+              Icon(Icons.access_alarm),
+              Icon(Icons.cake),
+            ],
+          ),
+        ),
+      ),
+    );
+
+    firstRect = tester.getRect(find.byType(RawMaterialButton).at(0));
+    expect(firstRect.width, 20.0);
+    expect(firstRect.height, 10.0);
+    secondRect = tester.getRect(find.byType(RawMaterialButton).at(1));
+    expect(secondRect.width, 20.0);
+    expect(secondRect.height, 10.0);
+    thirdRect = tester.getRect(find.byType(RawMaterialButton).at(2));
+    expect(thirdRect.width, 20.0);
+    expect(thirdRect.height, 10.0);
+  });
+
   testWidgets(
     'Default text/icon colors for enabled, selected and disabled states',
     (WidgetTester tester) async {
diff --git a/packages/flutter/test/material/toggle_buttons_theme_test.dart b/packages/flutter/test/material/toggle_buttons_theme_test.dart
index 6c75220..15ecacf 100644
--- a/packages/flutter/test/material/toggle_buttons_theme_test.dart
+++ b/packages/flutter/test/material/toggle_buttons_theme_test.dart
@@ -25,6 +25,7 @@
   test('ToggleButtonsThemeData defaults', () {
     const ToggleButtonsThemeData themeData = ToggleButtonsThemeData();
     expect(themeData.textStyle, null);
+    expect(themeData.constraints, null);
     expect(themeData.color, null);
     expect(themeData.selectedColor, null);
     expect(themeData.disabledColor, null);
@@ -41,6 +42,7 @@
 
     const ToggleButtonsTheme theme = ToggleButtonsTheme(data: ToggleButtonsThemeData());
     expect(theme.data.textStyle, null);
+    expect(theme.data.constraints, null);
     expect(theme.data.color, null);
     expect(theme.data.selectedColor, null);
     expect(theme.data.disabledColor, null);
@@ -72,6 +74,7 @@
     final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
     const ToggleButtonsThemeData(
       textStyle: TextStyle(fontSize: 10),
+      constraints: BoxConstraints(minHeight: 10.0, maxHeight: 20.0),
       color: Color(0xfffffff0),
       selectedColor: Color(0xfffffff1),
       disabledColor: Color(0xfffffff2),
@@ -95,6 +98,7 @@
      expect(description, <String>[
       'textStyle.inherit: true',
       'textStyle.size: 10.0',
+      'constraints: BoxConstraints(0.0<=w<=Infinity, 10.0<=h<=20.0)',
       'color: Color(0xfffffff0)',
       'selectedColor: Color(0xfffffff1)',
       'disabledColor: Color(0xfffffff2)',
@@ -154,6 +158,78 @@
     expect(textStyle.color, isNot(Colors.orange));
   });
 
+  testWidgets('Custom BoxConstraints', (WidgetTester tester) async {
+    // Test for minimum constraints
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: ToggleButtonsTheme(
+            data: const ToggleButtonsThemeData(
+              constraints: BoxConstraints(
+                minWidth: 50.0,
+                minHeight: 60.0,
+              ),
+            ),
+            child: ToggleButtons(
+              isSelected: const <bool>[false, false, false],
+              onPressed: (int index) {},
+              children: const <Widget>[
+                Icon(Icons.check),
+                Icon(Icons.access_alarm),
+                Icon(Icons.cake),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    Rect firstRect = tester.getRect(find.byType(RawMaterialButton).at(0));
+    expect(firstRect.width, 50.0);
+    expect(firstRect.height, 60.0);
+    Rect secondRect = tester.getRect(find.byType(RawMaterialButton).at(1));
+    expect(secondRect.width, 50.0);
+    expect(secondRect.height, 60.0);
+    Rect thirdRect = tester.getRect(find.byType(RawMaterialButton).at(2));
+    expect(thirdRect.width, 50.0);
+    expect(thirdRect.height, 60.0);
+
+    // Test for maximum constraints
+    await tester.pumpWidget(
+      Material(
+        child: boilerplate(
+          child: ToggleButtonsTheme(
+            data: const ToggleButtonsThemeData(
+              constraints: BoxConstraints(
+                maxWidth: 20.0,
+                maxHeight: 10.0,
+              ),
+            ),
+            child: ToggleButtons(
+              isSelected: const <bool>[false, false, false],
+              onPressed: (int index) {},
+              children: const <Widget>[
+                Icon(Icons.check),
+                Icon(Icons.access_alarm),
+                Icon(Icons.cake),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+
+    firstRect = tester.getRect(find.byType(RawMaterialButton).at(0));
+    expect(firstRect.width, 20.0);
+    expect(firstRect.height, 10.0);
+    secondRect = tester.getRect(find.byType(RawMaterialButton).at(1));
+    expect(secondRect.width, 20.0);
+    expect(secondRect.height, 10.0);
+    thirdRect = tester.getRect(find.byType(RawMaterialButton).at(2));
+    expect(thirdRect.width, 20.0);
+    expect(thirdRect.height, 10.0);
+  });
+
   testWidgets(
     'Theme text/icon colors for enabled, selected and disabled states',
     (WidgetTester tester) async {