added enableFeedback property to ListTile (#69088)

diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart
index c9fd36d..9a2310b 100644
--- a/packages/flutter/lib/src/material/list_tile.dart
+++ b/packages/flutter/lib/src/material/list_tile.dart
@@ -52,6 +52,7 @@
     this.contentPadding,
     this.tileColor,
     this.selectedTileColor,
+    this.enableFeedback,
     required Widget child,
   }) : super(key: key, child: child);
 
@@ -70,6 +71,7 @@
     EdgeInsetsGeometry? contentPadding,
     Color? tileColor,
     Color? selectedTileColor,
+    bool? enableFeedback,
     required Widget child,
   }) {
     assert(child != null);
@@ -87,6 +89,7 @@
           contentPadding: contentPadding ?? parent.contentPadding,
           tileColor: tileColor ?? parent.tileColor,
           selectedTileColor: selectedTileColor ?? parent.selectedTileColor,
+          enableFeedback: enableFeedback ?? parent.enableFeedback,
           child: child,
         );
       },
@@ -131,6 +134,11 @@
   /// If [ListTile.selectedTileColor] is provided, [selectedTileColor] is ignored.
   final Color? selectedTileColor;
 
+  /// If specified, defines the feedback property for `ListTile`.
+  ///
+  /// If [ListTile.enableFeedback] is provided, [enableFeedback] is ignored.
+  final bool? enableFeedback;
+
   /// The closest instance of this class that encloses the given context.
   ///
   /// Typical usage is as follows:
@@ -155,6 +163,7 @@
       contentPadding: contentPadding,
       tileColor: tileColor,
       selectedTileColor: selectedTileColor,
+      enableFeedback: enableFeedback,
       child: child,
     );
   }
@@ -169,7 +178,8 @@
         || textColor != oldWidget.textColor
         || contentPadding != oldWidget.contentPadding
         || tileColor != oldWidget.tileColor
-        || selectedTileColor != oldWidget.selectedTileColor;
+        || selectedTileColor != oldWidget.selectedTileColor
+        || enableFeedback != oldWidget.enableFeedback;
   }
 }
 
@@ -708,6 +718,7 @@
     this.autofocus = false,
     this.tileColor,
     this.selectedTileColor,
+    this.enableFeedback,
   }) : assert(isThreeLine != null),
        assert(enabled != null),
        assert(selected != null),
@@ -901,6 +912,16 @@
   /// if it's not null and to [Colors.transparent] if it's null.
   final Color? selectedTileColor;
 
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool? enableFeedback;
+
   /// Add a one pixel border in between each tile. If color isn't specified the
   /// [ThemeData.dividerColor] of the context's [Theme] is used.
   ///
@@ -1068,6 +1089,7 @@
 
     const EdgeInsets _defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0);
     final TextDirection textDirection = Directionality.of(context);
+    final bool resolvedEnableFeedback = enableFeedback ?? tileTheme.enableFeedback ?? true;
     final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
       ?? tileTheme.contentPadding?.resolve(textDirection)
       ?? _defaultContentPadding;
@@ -1090,6 +1112,7 @@
       focusColor: focusColor,
       hoverColor: hoverColor,
       autofocus: autofocus,
+      enableFeedback: resolvedEnableFeedback,
       child: Semantics(
         selected: selected,
         enabled: enabled,
diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart
index c03b297..130e7e5 100644
--- a/packages/flutter/test/material/list_tile_test.dart
+++ b/packages/flutter/test/material/list_tile_test.dart
@@ -13,6 +13,7 @@
 
 import '../rendering/mock_canvas.dart';
 import '../widgets/semantics_tester.dart';
+import 'feedback_tester.dart';
 
 class TestIcon extends StatefulWidget {
   const TestIcon({ Key? key }) : super(key: key);
@@ -1712,4 +1713,125 @@
     expect(renderBox.size.width, equals(0.0));
     expect(renderBox.size.height, equals(0.0));
   });
+
+  group('feedback', () {
+    late FeedbackTester feedback;
+
+    setUp(() {
+      feedback = FeedbackTester();
+    });
+
+    tearDown(() {
+      feedback.dispose();
+    });
+
+    testWidgets('ListTile with disabled feedback', (WidgetTester tester) async {
+      const bool enableFeedback = false;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: ListTile(
+              title: const Text('Title'),
+              onTap: () {},
+              enableFeedback: enableFeedback,
+            ),
+          ),
+        ),
+      );
+
+      await tester.tap(find.byType(ListTile));
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 0);
+      expect(feedback.hapticCount, 0);
+    });
+
+    testWidgets('ListTile with enabled feedback', (WidgetTester tester) async {
+      const bool enableFeedback = true;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: ListTile(
+              title: const Text('Title'),
+              onTap: () {},
+              enableFeedback: enableFeedback,
+            ),
+          ),
+        ),
+      );
+
+      await tester.tap(find.byType(ListTile));
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 1);
+      expect(feedback.hapticCount, 0);
+    });
+
+    testWidgets('ListTile with enabled feedback by default', (WidgetTester tester) async {
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: ListTile(
+              title: const Text('Title'),
+              onTap: () {},
+            ),
+          ),
+        ),
+      );
+
+      await tester.tap(find.byType(ListTile));
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 1);
+      expect(feedback.hapticCount, 0);
+    });
+
+    testWidgets('ListTile with disabled feedback using ListTileTheme', (WidgetTester tester) async {
+      const bool enableFeedbackTheme = false;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: ListTileTheme(
+              enableFeedback: enableFeedbackTheme,
+              child: ListTile(
+                title: const Text('Title'),
+                onTap: () {},
+              ),
+            ),
+          ),
+        ),
+      );
+
+      await tester.tap(find.byType(ListTile));
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 0);
+      expect(feedback.hapticCount, 0);
+    });
+
+    testWidgets('ListTile.enableFeedback overrides ListTileTheme.enableFeedback', (WidgetTester tester) async {
+      const bool enableFeedbackTheme = false;
+      const bool enableFeedback = true;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Material(
+            child: ListTileTheme(
+              enableFeedback: enableFeedbackTheme,
+              child: ListTile(
+                enableFeedback: enableFeedback,
+                title: const Text('Title'),
+                onTap: () {},
+              ),
+            ),
+          ),
+        ),
+      );
+
+      await tester.tap(find.byType(ListTile));
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 1);
+      expect(feedback.hapticCount, 0);
+    });
+  });
 }