From c4b11d801ade847bd481b74e23e06ea89f1ee37a Mon Sep 17 00:00:00 2001 From: hangyu Date: Thu, 9 Nov 2023 12:10:04 -0800 Subject: [PATCH] Make it possible to disable tapping to dismiss a tooltip. (#137375) issue: https://github.com/flutter/flutter/issues/137438 Use case: I want to add action button is tooltip and thus want to disable tap to dismiss a tooltip. ![image](https://github.com/flutter/flutter/assets/108393416/39c606fd-d301-4ed4-a411-4916823e5757) --- .../flutter/lib/src/material/tooltip.dart | 9 +++++ .../flutter/test/material/tooltip_test.dart | 35 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index f3cc5fc0f43..1e0cf8a0bda 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -185,6 +185,7 @@ class Tooltip extends StatefulWidget { this.textAlign, this.waitDuration, this.showDuration, + this.enableTapToDismiss = true, this.triggerMode, this.enableFeedback, this.onTriggered, @@ -307,6 +308,11 @@ class Tooltip extends StatefulWidget { /// for mouse pointer exits the widget. final Duration? showDuration; + /// Whether the tooltip can be dismissed by tap. + /// + /// The default value is true. + final bool enableTapToDismiss; + /// The [TooltipTriggerMode] that will show the tooltip. /// /// If this property is null, then [TooltipThemeData.triggerMode] is used. @@ -581,6 +587,9 @@ class TooltipState extends State with SingleTickerProviderStateMixin { // The primary pointer is not part of a "trigger" gesture so the tooltip // should be dismissed. void _handleTapToDismiss() { + if (!widget.enableTapToDismiss) { + return ; + } _scheduleDismissTooltip(withDelay: Duration.zero); _activeHoveringPointerDevices.clear(); } diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 5fe18466a7d..30fc62325fb 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -1016,6 +1016,39 @@ void main() { expect(find.text(tooltipText), findsNothing); }); + testWidgetsWithLeakTracking('Tooltip is dismissed after tap to dismiss immediately', (WidgetTester tester) async { + + await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap); + + final Finder tooltip = find.byType(Tooltip); + expect(find.text(tooltipText), findsNothing); + + // Tap to trigger the tooltip. + await _testGestureTap(tester, tooltip); + expect(find.text(tooltipText), findsOneWidget); + + // Tap to dismiss the tooltip. Tooltip is dismissed immediately. + await _testGestureTap(tester, find.text(tooltipText)); + await tester.pump(const Duration(milliseconds: 10)); + expect(find.text(tooltipText), findsNothing); + }); + + testWidgetsWithLeakTracking('Tooltip is not dismissed after tap if enableTapToDismiss is false', (WidgetTester tester) async { + await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, enableTapToDismiss: false); + + final Finder tooltip = find.byType(Tooltip); + expect(find.text(tooltipText), findsNothing); + + // Tap to trigger the tooltip. + await _testGestureTap(tester, tooltip); + expect(find.text(tooltipText), findsOneWidget); + + // Tap the tooltip. Tooltip is not dismissed . + await _testGestureTap(tester, find.text(tooltipText)); + await tester.pump(const Duration(milliseconds: 10)); + expect(find.text(tooltipText), findsOneWidget); + }); + testWidgetsWithLeakTracking('Tooltip is dismissed after a tap and showDuration expired when competing with a GestureDetector', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/98854 const Duration showDuration = Duration(seconds: 3); @@ -2480,6 +2513,7 @@ Future setWidgetForTooltipMode( WidgetTester tester, TooltipTriggerMode triggerMode, { Duration? showDuration, + bool? enableTapToDismiss, TooltipTriggeredCallback? onTriggered, }) async { await tester.pumpWidget( @@ -2489,6 +2523,7 @@ Future setWidgetForTooltipMode( triggerMode: triggerMode, onTriggered: onTriggered, showDuration: showDuration, + enableTapToDismiss: enableTapToDismiss ?? true, child: const SizedBox(width: 100.0, height: 100.0), ), ),