[CP] Add ability to customize `NavigationBar` indicator overlay and fix in… (#139162)

…dicator shape for the overlay (#138901)

fixes [Provide ability to override `NavigationBar` indicator ink response overlay](https://github.com/flutter/flutter/issues/138850) fixes [`NavigationBar.indicatorShape` is ignored, `NavigationBarThemeData.indicatorShape` is applied to the indicator inkwell](https://github.com/flutter/flutter/issues/138900)

---

Cherry pick fixes https://github.com/flutter/flutter/issues/139159

---

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        bottomNavigationBar: NavigationBarExample(),
      ),
    );
  }
}

class NavigationBarExample extends StatefulWidget {
  const NavigationBarExample({super.key});

  @override
  State<NavigationBarExample> createState() => _NavigationBarExampleState();
}

class _NavigationBarExampleState extends State<NavigationBarExample> {
  int index = 0;

  @override
  Widget build(BuildContext context) {
    return NavigationBar(
      elevation: 0,
      overlayColor: const MaterialStatePropertyAll<Color>(Colors.transparent),
      // indicatorShape: RoundedRectangleBorder(
      //   borderRadius: BorderRadius.circular(4.0),
      // ),
      indicatorColor: Colors.transparent,
      selectedIndex: index,
      onDestinationSelected: (int index) {
        setState(() {
          this.index = index;
        });
      },
      destinations: const <Widget>[
        NavigationDestination(
          selectedIcon: Icon(Icons.home_filled),
          icon: Icon(Icons.home_outlined),
          label: 'Home',
        ),
        NavigationDestination(
          selectedIcon: Icon(Icons.favorite),
          icon: Icon(Icons.favorite_outline),
          label: 'Favorites',
        ),
      ],
    );
  }
}

```

</details>

### Before

#### Cannot override `NavigationBar` Indicator ink well overlay

![Screenshot 2023-11-22 at 18 22 48](https://github.com/flutter/flutter/assets/48603081/06f54335-71ee-4882-afb0-53b614933c38)

#### Indicator shape is ignored for the indicator overlay

![Screenshot 2023-11-22 at 15 29 52](https://github.com/flutter/flutter/assets/48603081/913e0f77-48f4-4c6e-87f3-52c81b78f3d9)

### After

#### Can use `NavigationBar.overlayColor` or `NavigationBarThemeData.NavigationBar` to override default indicator overlay

`overlayColor: MaterialStatePropertyAll<Color>(Colors.red.withOpacity(0.33)),`

![Screenshot 2023-11-22 at 18 22 08](https://github.com/flutter/flutter/assets/48603081/28badae4-a7c7-4bf0-8bcc-278a1f84729d)

`overlayColor: MaterialStatePropertyAll<Color>(Colors.transparent),`

![Screenshot 2023-11-22 at 18 22 25](https://github.com/flutter/flutter/assets/48603081/674b48b1-f66a-4d91-9f10-ad307416ac32)

#### Indicator shape is respected for the indicator overlay

![Screenshot 2023-11-22 at 15 30 36](https://github.com/flutter/flutter/assets/48603081/ae9a3627-787e-45ac-9319-2ea8ea1e6ae6)

*Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.*

*List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.*

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart
index b209ffb..e40c624 100644
--- a/packages/flutter/lib/src/material/navigation_bar.dart
+++ b/packages/flutter/lib/src/material/navigation_bar.dart
@@ -98,6 +98,7 @@
     this.indicatorShape,
     this.height,
     this.labelBehavior,
+    this.overlayColor,
   }) :  assert(destinations.length >= 2),
         assert(0 <= selectedIndex && selectedIndex < destinations.length);
 
@@ -201,6 +202,10 @@
   /// [NavigationDestinationLabelBehavior.alwaysShow].
   final NavigationDestinationLabelBehavior? labelBehavior;
 
+  /// The highlight color that's typically used to indicate that
+  /// the [NavigationDestination] is focused, hovered, or pressed.
+  final MaterialStateProperty<Color?>? overlayColor;
+
   VoidCallback _handleTap(int index) {
     return onDestinationSelected != null
       ? () => onDestinationSelected!(index)
@@ -243,6 +248,7 @@
                         labelBehavior: effectiveLabelBehavior,
                         indicatorColor: indicatorColor,
                         indicatorShape: indicatorShape,
+                        overlayColor: overlayColor,
                         onTap: _handleTap(i),
                         child: destinations[i],
                       );
@@ -503,7 +509,8 @@
         child: _IndicatorInkWell(
           iconKey: iconKey,
           labelBehavior: info.labelBehavior,
-          customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
+          customBorder: info.indicatorShape ?? navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
+          overlayColor: info.overlayColor ?? navigationBarTheme.overlayColor,
           onTap: widget.enabled ? info.onTap : null,
           child: Row(
             children: <Widget>[
@@ -526,6 +533,7 @@
   const _IndicatorInkWell({
     required this.iconKey,
     required this.labelBehavior,
+    super.overlayColor,
     super.customBorder,
     super.onTap,
     super.child,
@@ -563,6 +571,7 @@
     required this.labelBehavior,
     required this.indicatorColor,
     required this.indicatorShape,
+    required this.overlayColor,
     required this.onTap,
     required super.child,
   });
@@ -629,6 +638,12 @@
   /// This is used by destinations to override the indicator shape.
   final ShapeBorder? indicatorShape;
 
+  /// The highlight color that's typically used to indicate that
+  /// the [NavigationDestination] is focused, hovered, or pressed.
+  ///
+  /// This is used by destinations to override the overlay color.
+  final MaterialStateProperty<Color?>? overlayColor;
+
   /// The callback that should be called when this destination is tapped.
   ///
   /// This is computed by calling [NavigationBar.onDestinationSelected]
diff --git a/packages/flutter/lib/src/material/navigation_bar_theme.dart b/packages/flutter/lib/src/material/navigation_bar_theme.dart
index 2de555f..adb9e39 100644
--- a/packages/flutter/lib/src/material/navigation_bar_theme.dart
+++ b/packages/flutter/lib/src/material/navigation_bar_theme.dart
@@ -52,6 +52,7 @@
     this.labelTextStyle,
     this.iconTheme,
     this.labelBehavior,
+    this.overlayColor,
   });
 
   /// Overrides the default value of [NavigationBar.height].
@@ -91,6 +92,9 @@
   /// Overrides the default value of [NavigationBar.labelBehavior].
   final NavigationDestinationLabelBehavior? labelBehavior;
 
+  /// Overrides the default value of [NavigationBar.overlayColor].
+  final MaterialStateProperty<Color?>? overlayColor;
+
   /// Creates a copy of this object with the given fields replaced with the
   /// new values.
   NavigationBarThemeData copyWith({
@@ -104,6 +108,7 @@
     MaterialStateProperty<TextStyle?>? labelTextStyle,
     MaterialStateProperty<IconThemeData?>? iconTheme,
     NavigationDestinationLabelBehavior? labelBehavior,
+    MaterialStateProperty<Color?>? overlayColor,
   }) {
     return NavigationBarThemeData(
       height: height ?? this.height,
@@ -116,6 +121,7 @@
       labelTextStyle: labelTextStyle ?? this.labelTextStyle,
       iconTheme: iconTheme ?? this.iconTheme,
       labelBehavior: labelBehavior ?? this.labelBehavior,
+      overlayColor: overlayColor ?? this.overlayColor,
     );
   }
 
@@ -139,6 +145,7 @@
       labelTextStyle: MaterialStateProperty.lerp<TextStyle?>(a?.labelTextStyle, b?.labelTextStyle, t, TextStyle.lerp),
       iconTheme: MaterialStateProperty.lerp<IconThemeData?>(a?.iconTheme, b?.iconTheme, t, IconThemeData.lerp),
       labelBehavior: t < 0.5 ? a?.labelBehavior : b?.labelBehavior,
+      overlayColor: MaterialStateProperty.lerp<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
     );
   }
 
@@ -154,6 +161,7 @@
     labelTextStyle,
     iconTheme,
     labelBehavior,
+    overlayColor,
   );
 
   @override
@@ -165,16 +173,17 @@
       return false;
     }
     return other is NavigationBarThemeData
-        && other.height == height
-        && other.backgroundColor == backgroundColor
-        && other.elevation == elevation
-        && other.shadowColor == shadowColor
-        && other.surfaceTintColor == surfaceTintColor
-        && other.indicatorColor == indicatorColor
-        && other.indicatorShape == indicatorShape
-        && other.labelTextStyle == labelTextStyle
-        && other.iconTheme == iconTheme
-        && other.labelBehavior == labelBehavior;
+      && other.height == height
+      && other.backgroundColor == backgroundColor
+      && other.elevation == elevation
+      && other.shadowColor == shadowColor
+      && other.surfaceTintColor == surfaceTintColor
+      && other.indicatorColor == indicatorColor
+      && other.indicatorShape == indicatorShape
+      && other.labelTextStyle == labelTextStyle
+      && other.iconTheme == iconTheme
+      && other.labelBehavior == labelBehavior
+      && other.overlayColor == overlayColor;
   }
 
   @override
@@ -190,6 +199,7 @@
     properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle?>>('labelTextStyle', labelTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<MaterialStateProperty<IconThemeData?>>('iconTheme', iconTheme, defaultValue: null));
     properties.add(DiagnosticsProperty<NavigationDestinationLabelBehavior>('labelBehavior', labelBehavior, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
   }
 }
 
diff --git a/packages/flutter/test/material/navigation_bar_test.dart b/packages/flutter/test/material/navigation_bar_test.dart
index 598b9a3..9e01060 100644
--- a/packages/flutter/test/material/navigation_bar_test.dart
+++ b/packages/flutter/test/material/navigation_bar_test.dart
@@ -12,6 +12,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
 
@@ -937,7 +938,7 @@
   });
 
   testWidgetsWithLeakTracking('Material3 - Navigation destination updates indicator color and shape', (WidgetTester tester) async {
-    final ThemeData theme = ThemeData(useMaterial3: true);
+    final ThemeData theme = ThemeData();
     const Color color = Color(0xff0000ff);
     const ShapeBorder shape = RoundedRectangleBorder();
 
@@ -945,20 +946,22 @@
       return MaterialApp(
         theme: theme,
         home: Scaffold(
-          bottomNavigationBar: NavigationBar(
-            indicatorColor: indicatorColor,
-            indicatorShape: indicatorShape,
-            destinations: const <Widget>[
-              NavigationDestination(
-                icon: Icon(Icons.ac_unit),
-                label: 'AC',
-              ),
-              NavigationDestination(
-                icon: Icon(Icons.access_alarm),
-                label: 'Alarm',
-              ),
-            ],
-            onDestinationSelected: (int i) { },
+          bottomNavigationBar: RepaintBoundary(
+            child: NavigationBar(
+              indicatorColor: indicatorColor,
+              indicatorShape: indicatorShape,
+              destinations: const <Widget>[
+                NavigationDestination(
+                  icon: Icon(Icons.ac_unit),
+                  label: 'AC',
+                ),
+                NavigationDestination(
+                  icon: Icon(Icons.access_alarm),
+                  label: 'Alarm',
+                ),
+              ],
+              onDestinationSelected: (int i) { },
+            ),
           ),
         ),
       );
@@ -970,11 +973,22 @@
     expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer);
     expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder());
 
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
+    await gesture.addPointer();
+    await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
+    await tester.pumpAndSettle();
+
+    // Test default indicator color and shape with ripple.
+    await expectLater(find.byType(NavigationBar), matchesGoldenFile('m3.navigation_bar.default.indicator.inkwell.shape.png'));
+
     await tester.pumpWidget(buildNavigationBar(indicatorColor: color, indicatorShape: shape));
 
     // Test custom indicator color and shape.
     expect(_getIndicatorDecoration(tester)?.color, color);
     expect(_getIndicatorDecoration(tester)?.shape, shape);
+
+    // Test custom indicator color and shape with ripple.
+    await expectLater(find.byType(NavigationBar), matchesGoldenFile('m3.navigation_bar.custom.indicator.inkwell.shape.png'));
   });
 
   testWidgetsWithLeakTracking('Destinations respect their disabled state', (WidgetTester tester) async {
@@ -1014,6 +1028,86 @@
     expect(selectedIndex, 1);
   });
 
+  testWidgetsWithLeakTracking('NavigationBar respects overlayColor in active/pressed/hovered states', (WidgetTester tester) async {
+    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
+    const Color hoverColor = Color(0xff0000ff);
+    const Color focusColor = Color(0xff00ffff);
+    const Color pressedColor = Color(0xffff00ff);
+    final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) {
+        if (states.contains(MaterialState.hovered)) {
+          return hoverColor;
+        }
+        if (states.contains(MaterialState.focused)) {
+          return focusColor;
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return pressedColor;
+        }
+        return Colors.transparent;
+    });
+
+    await tester.pumpWidget(MaterialApp(
+      home: Scaffold(
+        bottomNavigationBar: RepaintBoundary(
+          child: NavigationBar(
+            overlayColor: overlayColor,
+            destinations: const <Widget>[
+              NavigationDestination(
+                icon: Icon(Icons.ac_unit),
+                label: 'AC',
+              ),
+              NavigationDestination(
+                icon: Icon(Icons.access_alarm),
+                label: 'Alarm',
+              ),
+            ],
+            onDestinationSelected: (int i) { },
+          ),
+        ),
+      ),
+    ));
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
+    await gesture.addPointer();
+    await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
+    await tester.pumpAndSettle();
+
+    final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
+
+    // Test hovered state.
+    expect(
+      inkFeatures,
+      kIsWeb
+        ? (paints..rrect()..rrect()..circle(color: hoverColor))
+        : (paints..circle(color: hoverColor)),
+    );
+
+    await gesture.down(tester.getCenter(find.byType(NavigationIndicator).last));
+    await tester.pumpAndSettle();
+
+    // Test pressed state.
+    expect(
+      inkFeatures,
+      kIsWeb
+        ? (paints..circle()..circle()..circle(color: pressedColor))
+        : (paints..circle()..circle(color: pressedColor)),
+    );
+
+    await gesture.up();
+    await tester.pumpAndSettle();
+
+    // Press tab to focus the navigation bar.
+    await tester.sendKeyEvent(LogicalKeyboardKey.tab);
+    await tester.pumpAndSettle();
+
+    // Test focused state.
+    expect(
+      inkFeatures,
+      kIsWeb ? (paints..circle()..circle(color: focusColor)) : (paints..circle()..circle(color: focusColor)),
+    );
+  });
+
   group('Material 2', () {
     // These tests are only relevant for Material 2. Once Material 2
     // support is deprecated and the APIs are removed, these tests
diff --git a/packages/flutter/test/material/navigation_bar_theme_test.dart b/packages/flutter/test/material/navigation_bar_theme_test.dart
index 71d27ba..028c0ff 100644
--- a/packages/flutter/test/material/navigation_bar_theme_test.dart
+++ b/packages/flutter/test/material/navigation_bar_theme_test.dart
@@ -7,9 +7,11 @@
 @Tags(<String>['reduced-test-set'])
 library;
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
 
@@ -48,6 +50,7 @@
       labelTextStyle: MaterialStatePropertyAll<TextStyle>(TextStyle(fontSize: 7.0)),
       iconTheme: MaterialStatePropertyAll<IconThemeData>(IconThemeData(color: Color(0x00000097))),
       labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
+      overlayColor: MaterialStatePropertyAll<Color>(Color(0x00000096)),
     ).debugFillProperties(builder);
 
     final List<String> description = builder.properties
@@ -61,12 +64,11 @@
     expect(description[3], 'indicatorColor: Color(0x00000098)');
     expect(description[4], 'indicatorShape: CircleBorder(BorderSide(width: 0.0, style: none))');
     expect(description[5], 'labelTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 7.0))');
-
     // Ignore instance address for IconThemeData.
     expect(description[6].contains('iconTheme: MaterialStatePropertyAll(IconThemeData'), isTrue);
     expect(description[6].contains('(color: Color(0x00000097))'), isTrue);
-
     expect(description[7], 'labelBehavior: NavigationDestinationLabelBehavior.alwaysHide');
+    expect(description[8], 'overlayColor: MaterialStatePropertyAll(Color(0x00000096))');
   });
 
   testWidgetsWithLeakTracking('NavigationBarThemeData values are used when no NavigationBar properties are specified', (WidgetTester tester) async {
@@ -216,6 +218,86 @@
 
     await expectLater(find.byType(NavigationBar), matchesGoldenFile('indicator_custom_label_style.png'));
   });
+
+  testWidgetsWithLeakTracking('NavigationBar respects NavigationBarTheme.overlayColor in active/pressed/hovered states', (WidgetTester tester) async {
+    tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
+    const Color hoverColor = Color(0xff0000ff);
+    const Color focusColor = Color(0xff00ffff);
+    const Color pressedColor = Color(0xffff00ff);
+    final MaterialStateProperty<Color?> overlayColor = MaterialStateProperty.resolveWith<Color>(
+      (Set<MaterialState> states) {
+        if (states.contains(MaterialState.hovered)) {
+          return hoverColor;
+        }
+        if (states.contains(MaterialState.focused)) {
+          return focusColor;
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return pressedColor;
+        }
+        return Colors.transparent;
+    });
+
+    await tester.pumpWidget(MaterialApp(
+      theme: ThemeData(navigationBarTheme: NavigationBarThemeData(overlayColor: overlayColor)),
+      home: Scaffold(
+        bottomNavigationBar: RepaintBoundary(
+          child: NavigationBar(
+            destinations: const <Widget>[
+              NavigationDestination(
+                icon: Icon(Icons.ac_unit),
+                label: 'AC',
+              ),
+              NavigationDestination(
+                icon: Icon(Icons.access_alarm),
+                label: 'Alarm',
+              ),
+            ],
+            onDestinationSelected: (int i) { },
+          ),
+        ),
+      ),
+    ));
+
+    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
+    await gesture.addPointer();
+    await gesture.moveTo(tester.getCenter(find.byType(NavigationIndicator).last));
+    await tester.pumpAndSettle();
+
+    final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
+
+    // Test hovered state.
+    expect(
+      inkFeatures,
+      kIsWeb
+        ? (paints..rrect()..rrect()..circle(color: hoverColor))
+        : (paints..circle(color: hoverColor)),
+    );
+
+    await gesture.down(tester.getCenter(find.byType(NavigationIndicator).last));
+    await tester.pumpAndSettle();
+
+    // Test pressed state.
+    expect(
+      inkFeatures,
+      kIsWeb
+        ? (paints..circle()..circle()..circle(color: pressedColor))
+        : (paints..circle()..circle(color: pressedColor)),
+    );
+
+    await gesture.up();
+    await tester.pumpAndSettle();
+
+    // Press tab to focus the navigation bar.
+    await tester.sendKeyEvent(LogicalKeyboardKey.tab);
+    await tester.pumpAndSettle();
+
+    // Test focused state.
+    expect(
+      inkFeatures,
+      kIsWeb ? (paints..circle()..circle(color: focusColor)) : (paints..circle()..circle(color: focusColor)),
+    );
+  });
 }
 
 List<NavigationDestination> _destinations() {