Unverified Commit a2424602 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Make `Tooltip` state class public (#100553)

parent 2e5ad37b
...@@ -223,15 +223,15 @@ class Tooltip extends StatefulWidget { ...@@ -223,15 +223,15 @@ class Tooltip extends StatefulWidget {
/// * [Feedback], for providing platform-specific feedback to certain actions. /// * [Feedback], for providing platform-specific feedback to certain actions.
final bool? enableFeedback; final bool? enableFeedback;
static final List<_TooltipState> _openedTooltips = <_TooltipState>[]; static final List<TooltipState> _openedTooltips = <TooltipState>[];
// Causes any current tooltips to be concealed. Only called for mouse hover enter // Causes any current tooltips to be concealed. Only called for mouse hover enter
// detections. Won't conceal the supplied tooltip. // detections. Won't conceal the supplied tooltip.
static void _concealOtherTooltips(_TooltipState current) { static void _concealOtherTooltips(TooltipState current) {
if (_openedTooltips.isNotEmpty) { if (_openedTooltips.isNotEmpty) {
// Avoid concurrent modification. // Avoid concurrent modification.
final List<_TooltipState> openedTooltips = _openedTooltips.toList(); final List<TooltipState> openedTooltips = _openedTooltips.toList();
for (final _TooltipState state in openedTooltips) { for (final TooltipState state in openedTooltips) {
if (state == current) { if (state == current) {
continue; continue;
} }
...@@ -255,8 +255,8 @@ class Tooltip extends StatefulWidget { ...@@ -255,8 +255,8 @@ class Tooltip extends StatefulWidget {
static bool dismissAllToolTips() { static bool dismissAllToolTips() {
if (_openedTooltips.isNotEmpty) { if (_openedTooltips.isNotEmpty) {
// Avoid concurrent modification. // Avoid concurrent modification.
final List<_TooltipState> openedTooltips = _openedTooltips.toList(); final List<TooltipState> openedTooltips = _openedTooltips.toList();
for (final _TooltipState state in openedTooltips) { for (final TooltipState state in openedTooltips) {
state._dismissTooltip(immediately: true); state._dismissTooltip(immediately: true);
} }
return true; return true;
...@@ -265,7 +265,7 @@ class Tooltip extends StatefulWidget { ...@@ -265,7 +265,7 @@ class Tooltip extends StatefulWidget {
} }
@override @override
State<Tooltip> createState() => _TooltipState(); State<Tooltip> createState() => TooltipState();
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
...@@ -295,7 +295,11 @@ class Tooltip extends StatefulWidget { ...@@ -295,7 +295,11 @@ class Tooltip extends StatefulWidget {
} }
} }
class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { /// Contains the state for a [Tooltip].
///
/// This class can be used to programmatically show the Tooltip, see the
/// [ensureTooltipVisible] method.
class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
static const double _defaultVerticalOffset = 24.0; static const double _defaultVerticalOffset = 24.0;
static const bool _defaultPreferBelow = true; static const bool _defaultPreferBelow = true;
static const EdgeInsetsGeometry _defaultMargin = EdgeInsets.zero; static const EdgeInsetsGeometry _defaultMargin = EdgeInsets.zero;
...@@ -308,25 +312,25 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -308,25 +312,25 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
static const TooltipTriggerMode _defaultTriggerMode = TooltipTriggerMode.longPress; static const TooltipTriggerMode _defaultTriggerMode = TooltipTriggerMode.longPress;
static const bool _defaultEnableFeedback = true; static const bool _defaultEnableFeedback = true;
late double height; late double _height;
late EdgeInsetsGeometry padding; late EdgeInsetsGeometry _padding;
late EdgeInsetsGeometry margin; late EdgeInsetsGeometry _margin;
late Decoration decoration; late Decoration _decoration;
late TextStyle textStyle; late TextStyle _textStyle;
late double verticalOffset; late double _verticalOffset;
late bool preferBelow; late bool _preferBelow;
late bool excludeFromSemantics; late bool _excludeFromSemantics;
late AnimationController _controller; late AnimationController _controller;
OverlayEntry? _entry; OverlayEntry? _entry;
Timer? _dismissTimer; Timer? _dismissTimer;
Timer? _showTimer; Timer? _showTimer;
late Duration showDuration; late Duration _showDuration;
late Duration hoverShowDuration; late Duration _hoverShowDuration;
late Duration waitDuration; late Duration _waitDuration;
late bool _mouseIsConnected; late bool _mouseIsConnected;
bool _pressActivated = false; bool _pressActivated = false;
late TooltipTriggerMode triggerMode; late TooltipTriggerMode _triggerMode;
late bool enableFeedback; late bool _enableFeedback;
late bool _isConcealed; late bool _isConcealed;
late bool _forceRemoval; late bool _forceRemoval;
late bool _visible; late bool _visible;
...@@ -436,9 +440,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -436,9 +440,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
// still concealed or not. // still concealed or not.
_forceRemoval = true; _forceRemoval = true;
if (_pressActivated) { if (_pressActivated) {
_dismissTimer ??= Timer(showDuration, _controller.reverse); _dismissTimer ??= Timer(_showDuration, _controller.reverse);
} else { } else {
_dismissTimer ??= Timer(hoverShowDuration, _controller.reverse); _dismissTimer ??= Timer(_hoverShowDuration, _controller.reverse);
} }
_pressActivated = false; _pressActivated = false;
} }
...@@ -450,7 +454,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -450,7 +454,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
ensureTooltipVisible(); ensureTooltipVisible();
return; return;
} }
_showTimer ??= Timer(waitDuration, ensureTooltipVisible); _showTimer ??= Timer(_waitDuration, ensureTooltipVisible);
} }
void _concealTooltip() { void _concealTooltip() {
...@@ -519,7 +523,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -519,7 +523,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
return true; return true;
} }
static final Set<_TooltipState> _mouseIn = <_TooltipState>{}; static final Set<TooltipState> _mouseIn = <TooltipState>{};
void _handleMouseEnter() { void _handleMouseEnter() {
if (mounted) { if (mounted) {
...@@ -553,20 +557,20 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -553,20 +557,20 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
child: _TooltipOverlay( child: _TooltipOverlay(
richMessage: widget.richMessage ?? TextSpan(text: widget.message), richMessage: widget.richMessage ?? TextSpan(text: widget.message),
height: height, height: _height,
padding: padding, padding: _padding,
margin: margin, margin: _margin,
onEnter: _mouseIsConnected ? (_) => _handleMouseEnter() : null, onEnter: _mouseIsConnected ? (_) => _handleMouseEnter() : null,
onExit: _mouseIsConnected ? (_) => _handleMouseExit() : null, onExit: _mouseIsConnected ? (_) => _handleMouseExit() : null,
decoration: decoration, decoration: _decoration,
textStyle: textStyle, textStyle: _textStyle,
animation: CurvedAnimation( animation: CurvedAnimation(
parent: _controller, parent: _controller,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
), ),
target: target, target: target,
verticalOffset: verticalOffset, verticalOffset: _verticalOffset,
preferBelow: preferBelow, preferBelow: _preferBelow,
), ),
); );
_entry = OverlayEntry(builder: (BuildContext context) => overlay); _entry = OverlayEntry(builder: (BuildContext context) => overlay);
...@@ -632,8 +636,8 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -632,8 +636,8 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
void _handlePress() { void _handlePress() {
_pressActivated = true; _pressActivated = true;
final bool tooltipCreated = ensureTooltipVisible(); final bool tooltipCreated = ensureTooltipVisible();
if (tooltipCreated && enableFeedback) { if (tooltipCreated && _enableFeedback) {
if (triggerMode == TooltipTriggerMode.longPress) if (_triggerMode == TooltipTriggerMode.longPress)
Feedback.forLongPress(context); Feedback.forLongPress(context);
else else
Feedback.forTap(context); Feedback.forTap(context);
...@@ -673,22 +677,22 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -673,22 +677,22 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
); );
} }
height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight(); _height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight();
padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding(); _padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding();
margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin; _margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin;
verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset; _verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset;
preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow; _preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow;
excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics; _excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration; _decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration;
textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle; _textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle;
waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration; _waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration;
showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration; _showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration;
hoverShowDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultHoverShowDuration; _hoverShowDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultHoverShowDuration;
triggerMode = widget.triggerMode ?? tooltipTheme.triggerMode ?? _defaultTriggerMode; _triggerMode = widget.triggerMode ?? tooltipTheme.triggerMode ?? _defaultTriggerMode;
enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback; _enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback;
Widget result = Semantics( Widget result = Semantics(
label: excludeFromSemantics label: _excludeFromSemantics
? null ? null
: _tooltipMessage, : _tooltipMessage,
child: widget.child, child: widget.child,
...@@ -698,9 +702,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ...@@ -698,9 +702,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
if (_visible) { if (_visible) {
result = GestureDetector( result = GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onLongPress: (triggerMode == TooltipTriggerMode.longPress) ? onLongPress: (_triggerMode == TooltipTriggerMode.longPress) ?
_handlePress : null, _handlePress : null,
onTap: (triggerMode == TooltipTriggerMode.tap) ? _handlePress : null, onTap: (_triggerMode == TooltipTriggerMode.tap) ? _handlePress : null,
excludeFromSemantics: true, excludeFromSemantics: true,
child: result, child: result,
); );
......
...@@ -9,23 +9,6 @@ import 'package:flutter/material.dart'; ...@@ -9,23 +9,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.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'; const String tooltipText = 'TIP';
void main() { void main() {
...@@ -162,13 +145,13 @@ void main() { ...@@ -162,13 +145,13 @@ void main() {
}); });
testWidgets('Tooltip does not trigger manually when in TooltipVisibility with visible = false', (WidgetTester tester) async { testWidgets('Tooltip does not trigger manually when in TooltipVisibility with visible = false', (WidgetTester tester) async {
final GlobalKey key = GlobalKey(); final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: TooltipVisibility( home: TooltipVisibility(
visible: false, visible: false,
child: Tooltip( child: Tooltip(
key: key, key: tooltipKey,
message: tooltipText, message: tooltipText,
child: const SizedBox(width: 100.0, height: 100.0), child: const SizedBox(width: 100.0, height: 100.0),
), ),
...@@ -176,19 +159,19 @@ void main() { ...@@ -176,19 +159,19 @@ void main() {
), ),
); );
_ensureTooltipVisible(key); tooltipKey.currentState?.ensureTooltipVisible();
await tester.pump(); await tester.pump();
expect(find.text(tooltipText), findsNothing); expect(find.text(tooltipText), findsNothing);
}); });
testWidgets('Tooltip triggers manually when in TooltipVisibility with visible = true', (WidgetTester tester) async { testWidgets('Tooltip triggers manually when in TooltipVisibility with visible = true', (WidgetTester tester) async {
final GlobalKey key = GlobalKey(); final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: TooltipVisibility( home: TooltipVisibility(
visible: true, visible: true,
child: Tooltip( child: Tooltip(
key: key, key: tooltipKey,
message: tooltipText, message: tooltipText,
child: const SizedBox(width: 100.0, height: 100.0), child: const SizedBox(width: 100.0, height: 100.0),
), ),
...@@ -196,7 +179,7 @@ void main() { ...@@ -196,7 +179,7 @@ void main() {
), ),
); );
_ensureTooltipVisible(key); tooltipKey.currentState?.ensureTooltipVisible();
await tester.pump(); await tester.pump();
expect(find.text(tooltipText), findsOneWidget); expect(find.text(tooltipText), findsOneWidget);
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment