Added Checkbox support for MaterialStateBorderSide (#86910)

diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart
index 5632c3b..b54b46e 100644
--- a/packages/flutter/lib/src/material/checkbox.dart
+++ b/packages/flutter/lib/src/material/checkbox.dart
@@ -310,7 +310,23 @@
   final OutlinedBorder? shape;
 
   /// {@template flutter.material.checkbox.side}
-  /// The side of the checkbox's border.
+  /// The color and width of the checkbox's border.
+  ///
+  /// This property can be a [MaterialStateBorderSide] that can
+  /// specify different border color and widths depending on the
+  /// checkbox's state.
+  ///
+  /// Resolves in the following states:
+  ///  * [MaterialState.pressed].
+  ///  * [MaterialState.selected].
+  ///  * [MaterialState.hovered].
+  ///  * [MaterialState.focused].
+  ///  * [MaterialState.disabled].
+  ///
+  /// If this property is not a [MaterialStateBorderSide] and it is
+  /// non-null, then it is only rendered when the checkbox's value is
+  /// false. The difference in interpretation is for backwards
+  /// compatibility.
   /// {@endtemplate}
   ///
   /// If this property is null then [CheckboxThemeData.side] of [ThemeData.checkboxTheme]
@@ -383,6 +399,14 @@
     });
   }
 
+  BorderSide? _resolveSide(BorderSide? side) {
+    if (side is MaterialStateBorderSide)
+      return MaterialStateProperty.resolveAs<BorderSide?>(side, states);
+    if (!states.contains(MaterialState.selected))
+      return side;
+    return null;
+  }
+
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterial(context));
@@ -477,7 +501,7 @@
           ..shape = widget.shape ?? themeData.checkboxTheme.shape ?? const RoundedRectangleBorder(
               borderRadius: BorderRadius.all(Radius.circular(1.0)),
           )
-          ..side = widget.side ?? themeData.checkboxTheme.side,
+          ..side = _resolveSide(widget.side) ?? _resolveSide(themeData.checkboxTheme.side),
       ),
     );
   }
@@ -563,13 +587,13 @@
       ..strokeWidth = _kStrokeWidth;
   }
 
-  void _drawBorder(Canvas canvas, Rect outer, double t, Paint paint) {
-    assert(t >= 0.0 && t <= 0.5);
-    OutlinedBorder resolvedShape = shape;
-    if (side == null) {
-      resolvedShape = resolvedShape.copyWith(side: BorderSide(width: 2, color: paint.color));
+  void _drawBox(Canvas canvas, Rect outer, Paint paint, BorderSide? side, bool fill) {
+    if (fill) {
+      canvas.drawPath(shape.getOuterPath(outer), paint);
     }
-    resolvedShape.copyWith(side: side).paint(canvas, outer);
+    if (side != null) {
+      shape.copyWith(side: side).paint(canvas, outer);
+    }
   }
 
   void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
@@ -622,14 +646,13 @@
     if (previousValue == false || value == false) {
       final double t = value == false ? 1.0 - tNormalized : tNormalized;
       final Rect outer = _outerRectAt(origin, t);
-      final Path emptyCheckboxPath = shape.copyWith(side: side).getOuterPath(outer);
       final Paint paint = Paint()..color = _colorAt(t);
 
       if (t <= 0.5) {
-        _drawBorder(canvas, outer, t, paint);
+        final BorderSide border = side ?? BorderSide(width: 2, color: paint.color);
+        _drawBox(canvas, outer, paint, border, false); // only paint the border
       } else {
-        canvas.drawPath(emptyCheckboxPath, paint);
-
+        _drawBox(canvas, outer, paint, side, true);
         final double tShrink = (t - 0.5) * 2.0;
         if (previousValue == null || value == null)
           _drawDash(canvas, origin, tShrink, strokePaint);
@@ -639,8 +662,8 @@
     } else { // Two cases: null to true, true to null
       final Rect outer = _outerRectAt(origin, 1.0);
       final Paint paint = Paint() ..color = _colorAt(1.0);
-      canvas.drawPath(shape.copyWith(side: side).getOuterPath(outer), paint);
 
+      _drawBox(canvas, outer, paint, side, true);
       if (tNormalized <= 0.5) {
         final double tShrink = 1.0 - tNormalized * 2.0;
         if (previousValue == true)
diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart
index 6c03f41..e390e85 100644
--- a/packages/flutter/test/material/checkbox_test.dart
+++ b/packages/flutter/test/material/checkbox_test.dart
@@ -1193,6 +1193,122 @@
     // Release pointer after widget disappeared.
     await gesture.up();
   });
+
+  testWidgets('Checkbox BorderSide side only applies when unselected', (WidgetTester tester) async {
+    const Color borderColor = Color(0xfff44336);
+    const Color activeColor = Color(0xff123456);
+    const BorderSide side = BorderSide(
+      width: 4,
+      color: borderColor,
+    );
+
+    Widget buildApp({ bool? value, bool enabled = true }) {
+      return MaterialApp(
+        home: Material(
+          child: Center(
+            child: Checkbox(
+              value: value,
+              tristate: value == null,
+              activeColor: activeColor,
+              onChanged: enabled ? (bool? newValue) { } : null,
+              side: side,
+            ),
+          ),
+        ),
+      );
+    }
+
+    RenderBox getCheckboxRenderer() {
+      return tester.renderObject<RenderBox>(find.byType(Checkbox));
+    }
+
+    void expectBorder() {
+      expect(
+        getCheckboxRenderer(),
+        paints
+        ..drrect(
+          color: borderColor,
+          outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)),
+          inner: RRect.fromLTRBR(19, 19, 29, 29, const Radius.circular(-3)),
+        ),
+      );
+    }
+
+    // Checkbox is unselected, so the specified BorderSide appears.
+
+    await tester.pumpWidget(buildApp(value: false));
+    await tester.pumpAndSettle();
+    expectBorder();
+
+    await tester.pumpWidget(buildApp(value: false, enabled: false));
+    await tester.pumpAndSettle();
+    expectBorder();
+
+    // Checkbox is selected/interdeterminate, so the specified BorderSide
+    // does not appear.
+
+    await tester.pumpWidget(buildApp(value: true));
+    await tester.pumpAndSettle();
+    expect(getCheckboxRenderer(), isNot(paints..drrect())); // no border
+    expect(getCheckboxRenderer(), paints..path(color: activeColor)); // checkbox fill
+
+    await tester.pumpWidget(buildApp(value: null));
+    await tester.pumpAndSettle();
+    expect(getCheckboxRenderer(), isNot(paints..drrect())); // no border
+    expect(getCheckboxRenderer(), paints..path(color: activeColor)); // checkbox fill
+  });
+
+  testWidgets('Checkbox MaterialStateBorderSide applies unconditionally', (WidgetTester tester) async {
+    const Color borderColor = Color(0xfff44336);
+    const BorderSide side = BorderSide(
+      width: 4,
+      color: borderColor,
+    );
+
+    Widget buildApp({ bool? value, bool enabled = true }) {
+      return MaterialApp(
+        home: Material(
+          child: Center(
+            child: Checkbox(
+              value: value,
+              tristate: value == null,
+              onChanged: enabled ? (bool? newValue) { } : null,
+              side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) => side),
+            ),
+          ),
+        ),
+      );
+    }
+
+    void expectBorder() {
+      expect(
+        tester.renderObject<RenderBox>(find.byType(Checkbox)),
+        paints
+        ..drrect(
+          color: borderColor,
+          outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)),
+          inner: RRect.fromLTRBR(19, 19, 29, 29, const Radius.circular(-3)),
+        ),
+      );
+    }
+
+    await tester.pumpWidget(buildApp(value: false));
+    await tester.pumpAndSettle();
+    expectBorder();
+
+
+    await tester.pumpWidget(buildApp(value: false, enabled: false));
+    await tester.pumpAndSettle();
+    expectBorder();
+
+    await tester.pumpWidget(buildApp(value: true));
+    await tester.pumpAndSettle();
+    expectBorder();
+
+    await tester.pumpWidget(buildApp(value: null));
+    await tester.pumpAndSettle();
+    expectBorder();
+  });
 }
 
 class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {