Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
6b94f2f6
Unverified
Commit
6b94f2f6
authored
Oct 19, 2021
by
Markus Aksli
Committed by
GitHub
Oct 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add `TooltipVisibility` widget (#91609)
parent
d74cf314
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
325 additions
and
18 deletions
+325
-18
material.dart
packages/flutter/lib/material.dart
+1
-0
tooltip.dart
packages/flutter/lib/src/material/tooltip.dart
+34
-18
tooltip_theme.dart
packages/flutter/lib/src/material/tooltip_theme.dart
+4
-0
tooltip_visibility.dart
packages/flutter/lib/src/material/tooltip_visibility.dart
+63
-0
tooltip_visibility_test.dart
packages/flutter/test/material/tooltip_visibility_test.dart
+223
-0
No files found.
packages/flutter/lib/material.dart
View file @
6b94f2f6
...
...
@@ -158,6 +158,7 @@ export 'src/material/toggle_buttons_theme.dart';
export
'src/material/toggleable.dart'
;
export
'src/material/tooltip.dart'
;
export
'src/material/tooltip_theme.dart'
;
export
'src/material/tooltip_visibility.dart'
;
export
'src/material/typography.dart'
;
export
'src/material/user_accounts_drawer_header.dart'
;
export
'widgets.dart'
;
packages/flutter/lib/src/material/tooltip.dart
View file @
6b94f2f6
...
...
@@ -14,6 +14,7 @@ import 'colors.dart';
import
'feedback.dart'
;
import
'theme.dart'
;
import
'tooltip_theme.dart'
;
import
'tooltip_visibility.dart'
;
/// A material design tooltip.
///
...
...
@@ -69,6 +70,7 @@ import 'tooltip_theme.dart';
///
/// * <https://material.io/design/components/tooltips.html>
/// * [TooltipTheme] or [ThemeData.tooltipTheme]
/// * [TooltipVisibility]
class
Tooltip
extends
StatefulWidget
{
/// Creates a tooltip.
///
...
...
@@ -327,6 +329,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
late
bool
enableFeedback
;
late
bool
_isConcealed
;
late
bool
_forceRemoval
;
late
bool
_visible
;
/// The plain text message for this tooltip.
///
...
...
@@ -352,6 +355,12 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
GestureBinding
.
instance
!.
pointerRouter
.
addGlobalRoute
(
_handlePointerEvent
);
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
_visible
=
TooltipVisibility
.
of
(
context
);
}
// https://material.io/components/tooltips#specs
double
_getDefaultTooltipHeight
()
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
...
...
@@ -483,8 +492,11 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
/// Shows the tooltip if it is not already visible.
///
/// Returns `false` when the tooltip was already visible.
/// Returns `false` when the tooltip shouldn't be shown or when the tooltip
/// was already visible.
bool
ensureTooltipVisible
()
{
if
(!
_visible
)
return
false
;
_showTimer
?.
cancel
();
_showTimer
=
null
;
_forceRemoval
=
false
;
...
...
@@ -671,27 +683,31 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
triggerMode
=
widget
.
triggerMode
??
tooltipTheme
.
triggerMode
??
_defaultTriggerMode
;
enableFeedback
=
widget
.
enableFeedback
??
tooltipTheme
.
enableFeedback
??
_defaultEnableFeedback
;
Widget
result
=
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onLongPress:
(
triggerMode
==
TooltipTriggerMode
.
longPress
)
?
_handlePress
:
null
,
onTap:
(
triggerMode
==
TooltipTriggerMode
.
tap
)
?
_handlePress
:
null
,
excludeFromSemantics:
true
,
child:
Semantics
(
label:
excludeFromSemantics
?
null
:
_tooltipMessage
,
child:
widget
.
child
,
),
Widget
result
=
Semantics
(
label:
excludeFromSemantics
?
null
:
_tooltipMessage
,
child:
widget
.
child
,
);
// Only check for hovering if there is a mouse connected.
if
(
_mouseIsConnected
)
{
result
=
MouseRegion
(
onEnter:
(
_
)
=>
_handleMouseEnter
(),
onExit:
(
_
)
=>
_handleMouseExit
(),
// Only check for gestures if tooltip should be visible.
if
(
_visible
)
{
result
=
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onLongPress:
(
triggerMode
==
TooltipTriggerMode
.
longPress
)
?
_handlePress
:
null
,
onTap:
(
triggerMode
==
TooltipTriggerMode
.
tap
)
?
_handlePress
:
null
,
excludeFromSemantics:
true
,
child:
result
,
);
// Only check for hovering if there is a mouse connected.
if
(
_mouseIsConnected
)
{
result
=
MouseRegion
(
onEnter:
(
_
)
=>
_handleMouseEnter
(),
onExit:
(
_
)
=>
_handleMouseExit
(),
child:
result
,
);
}
}
return
result
;
...
...
packages/flutter/lib/src/material/tooltip_theme.dart
View file @
6b94f2f6
...
...
@@ -242,6 +242,10 @@ class TooltipThemeData with Diagnosticable {
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [TooltipVisibility], which can be used to visually disable descendant [Tooltip]s.
class
TooltipTheme
extends
InheritedTheme
{
/// Creates a tooltip theme that controls the configurations for
/// [Tooltip].
...
...
packages/flutter/lib/src/material/tooltip_visibility.dart
0 → 100644
View file @
6b94f2f6
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/widgets.dart'
;
class
_TooltipVisibilityScope
extends
InheritedWidget
{
const
_TooltipVisibilityScope
({
Key
?
key
,
required
Widget
child
,
required
this
.
visible
,
})
:
super
(
key:
key
,
child:
child
);
final
bool
visible
;
@override
bool
updateShouldNotify
(
_TooltipVisibilityScope
old
)
{
return
old
.
visible
!=
visible
;
}
}
/// Overrides the visibility of descendant [Tooltip] widgets.
///
/// If disabled, the descendant [Tooltip] widgets will not display a tooltip
/// when tapped, long-pressed, hovered by the mouse, or when
/// `ensureTooltipVisible` is called. This only visually disables tooltips but
/// continues to provide any semantic information that is provided.
class
TooltipVisibility
extends
StatelessWidget
{
/// Creates a widget that configures the visibility of [Tooltip].
///
/// Both arguments must not be null.
const
TooltipVisibility
({
Key
?
key
,
required
this
.
child
,
required
this
.
visible
,
})
:
super
(
key:
key
);
/// The widget below this widget in the tree.
///
/// The entire app can be wrapped in this widget to globally control [Tooltip]
/// visibility.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
child
;
/// Determines the visibility of [Tooltip] widgets that inherit from this widget.
final
bool
visible
;
/// The [visible] of the closest instance of this class that encloses the
/// given context. Defaults to `true` if none are found.
static
bool
of
(
BuildContext
context
)
{
final
_TooltipVisibilityScope
?
visibility
=
context
.
dependOnInheritedWidgetOfExactType
<
_TooltipVisibilityScope
>();
return
visibility
?.
visible
??
true
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
_TooltipVisibilityScope
(
child:
child
,
visible:
visible
,
);
}
}
packages/flutter/test/material/tooltip_visibility_test.dart
0 → 100644
View file @
6b94f2f6
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
_ensureTooltipVisible
(
GlobalKey
key
)
{
// This function uses "as dynamic" to defeat the static analysis. In general
// you want to avoid using this style in your code, as it will cause the
// analyzer to be unable to help you catch errors.
//
// In this case, we do it because we are trying to call internal methods of
// the tooltip code in order to test it. Normally, the state of a tooltip is a
// private class, but by using a GlobalKey we can get a handle to that object
// and by using "as dynamic" we can bypass the analyzer's type checks and call
// methods that we aren't supposed to be able to know about.
//
// It's ok to do this in tests, but you really don't want to do it in
// production code.
// ignore: avoid_dynamic_calls
(
key
.
currentState
as
dynamic
).
ensureTooltipVisible
();
}
const
String
tooltipText
=
'TIP'
;
void
main
(
)
{
testWidgets
(
'Tooltip does not build MouseRegion when mouse is detected and in TooltipVisibility with visibility = false'
,
(
WidgetTester
tester
)
async
{
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
TooltipVisibility
(
visible:
false
,
child:
Tooltip
(
message:
tooltipText
,
child:
SizedBox
(
width:
100.0
,
height:
100.0
,
),
),
),
),
);
expect
(
find
.
descendant
(
of:
find
.
byType
(
Tooltip
),
matching:
find
.
byType
(
MouseRegion
)),
findsNothing
);
});
testWidgets
(
'Tooltip does not show when hovered when in TooltipVisibility with visible = false'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Center
(
child:
TooltipVisibility
(
visible:
false
,
child:
Tooltip
(
message:
tooltipText
,
waitDuration:
waitDuration
,
child:
SizedBox
(
width:
100.0
,
height:
100.0
,
),
),
),
),
),
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
tooltip
));
await
tester
.
pump
();
// Wait for it to appear.
await
tester
.
pump
(
waitDuration
);
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip shows when hovered when in TooltipVisibility with visible = true'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
TestGesture
?
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Center
(
child:
TooltipVisibility
(
visible:
true
,
child:
Tooltip
(
message:
tooltipText
,
waitDuration:
waitDuration
,
child:
SizedBox
(
width:
100.0
,
height:
100.0
,
),
),
),
),
),
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
tooltip
));
await
tester
.
pump
();
// Wait for it to appear.
await
tester
.
pump
(
waitDuration
);
expect
(
find
.
text
(
tooltipText
),
findsOneWidget
);
// Wait for it to disappear.
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
await
gesture
.
removePointer
();
gesture
=
null
;
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip does not build GestureDetector when in TooltipVisibility with visibility = false'
,
(
WidgetTester
tester
)
async
{
await
setWidgetForTooltipMode
(
tester
,
TooltipTriggerMode
.
tap
,
false
);
expect
(
find
.
byType
(
GestureDetector
),
findsNothing
);
});
testWidgets
(
'Tooltip triggers on tap when trigger mode is tap and in TooltipVisibility with visible = true'
,
(
WidgetTester
tester
)
async
{
await
setWidgetForTooltipMode
(
tester
,
TooltipTriggerMode
.
tap
,
true
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
await
testGestureTap
(
tester
,
tooltip
);
expect
(
find
.
text
(
tooltipText
),
findsOneWidget
);
});
testWidgets
(
'Tooltip does not trigger manually when in TooltipVisibility with visible = false'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
TooltipVisibility
(
visible:
false
,
child:
Tooltip
(
key:
key
,
message:
tooltipText
,
child:
const
SizedBox
(
width:
100.0
,
height:
100.0
),
),
),
),
);
_ensureTooltipVisible
(
key
);
await
tester
.
pump
();
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip triggers manually when in TooltipVisibility with visible = true'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
TooltipVisibility
(
visible:
true
,
child:
Tooltip
(
key:
key
,
message:
tooltipText
,
child:
const
SizedBox
(
width:
100.0
,
height:
100.0
),
),
),
),
);
_ensureTooltipVisible
(
key
);
await
tester
.
pump
();
expect
(
find
.
text
(
tooltipText
),
findsOneWidget
);
});
}
Future
<
void
>
setWidgetForTooltipMode
(
WidgetTester
tester
,
TooltipTriggerMode
triggerMode
,
bool
visibility
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
TooltipVisibility
(
visible:
visibility
,
child:
Tooltip
(
message:
tooltipText
,
triggerMode:
triggerMode
,
child:
const
SizedBox
(
width:
100.0
,
height:
100.0
),
),
),
),
);
}
Future
<
void
>
testGestureTap
(
WidgetTester
tester
,
Finder
tooltip
)
async
{
await
tester
.
tap
(
tooltip
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
10
));
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment