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 {
/// * [Feedback], for providing platform-specific feedback to certain actions.
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
// detections. Won't conceal the supplied tooltip.
static void _concealOtherTooltips(_TooltipState current) {
static void _concealOtherTooltips(TooltipState current) {
if (_openedTooltips.isNotEmpty) {
// Avoid concurrent modification.
final List<_TooltipState> openedTooltips = _openedTooltips.toList();
for (final _TooltipState state in openedTooltips) {
final List<TooltipState> openedTooltips = _openedTooltips.toList();
for (final TooltipState state in openedTooltips) {
if (state == current) {
continue;
}
......@@ -255,8 +255,8 @@ class Tooltip extends StatefulWidget {
static bool dismissAllToolTips() {
if (_openedTooltips.isNotEmpty) {
// Avoid concurrent modification.
final List<_TooltipState> openedTooltips = _openedTooltips.toList();
for (final _TooltipState state in openedTooltips) {
final List<TooltipState> openedTooltips = _openedTooltips.toList();
for (final TooltipState state in openedTooltips) {
state._dismissTooltip(immediately: true);
}
return true;
......@@ -265,7 +265,7 @@ class Tooltip extends StatefulWidget {
}
@override
State<Tooltip> createState() => _TooltipState();
State<Tooltip> createState() => TooltipState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
......@@ -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 bool _defaultPreferBelow = true;
static const EdgeInsetsGeometry _defaultMargin = EdgeInsets.zero;
......@@ -308,25 +312,25 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
static const TooltipTriggerMode _defaultTriggerMode = TooltipTriggerMode.longPress;
static const bool _defaultEnableFeedback = true;
late double height;
late EdgeInsetsGeometry padding;
late EdgeInsetsGeometry margin;
late Decoration decoration;
late TextStyle textStyle;
late double verticalOffset;
late bool preferBelow;
late bool excludeFromSemantics;
late double _height;
late EdgeInsetsGeometry _padding;
late EdgeInsetsGeometry _margin;
late Decoration _decoration;
late TextStyle _textStyle;
late double _verticalOffset;
late bool _preferBelow;
late bool _excludeFromSemantics;
late AnimationController _controller;
OverlayEntry? _entry;
Timer? _dismissTimer;
Timer? _showTimer;
late Duration showDuration;
late Duration hoverShowDuration;
late Duration waitDuration;
late Duration _showDuration;
late Duration _hoverShowDuration;
late Duration _waitDuration;
late bool _mouseIsConnected;
bool _pressActivated = false;
late TooltipTriggerMode triggerMode;
late bool enableFeedback;
late TooltipTriggerMode _triggerMode;
late bool _enableFeedback;
late bool _isConcealed;
late bool _forceRemoval;
late bool _visible;
......@@ -436,9 +440,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
// still concealed or not.
_forceRemoval = true;
if (_pressActivated) {
_dismissTimer ??= Timer(showDuration, _controller.reverse);
_dismissTimer ??= Timer(_showDuration, _controller.reverse);
} else {
_dismissTimer ??= Timer(hoverShowDuration, _controller.reverse);
_dismissTimer ??= Timer(_hoverShowDuration, _controller.reverse);
}
_pressActivated = false;
}
......@@ -450,7 +454,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
ensureTooltipVisible();
return;
}
_showTimer ??= Timer(waitDuration, ensureTooltipVisible);
_showTimer ??= Timer(_waitDuration, ensureTooltipVisible);
}
void _concealTooltip() {
......@@ -519,7 +523,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
return true;
}
static final Set<_TooltipState> _mouseIn = <_TooltipState>{};
static final Set<TooltipState> _mouseIn = <TooltipState>{};
void _handleMouseEnter() {
if (mounted) {
......@@ -553,20 +557,20 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
textDirection: Directionality.of(context),
child: _TooltipOverlay(
richMessage: widget.richMessage ?? TextSpan(text: widget.message),
height: height,
padding: padding,
margin: margin,
height: _height,
padding: _padding,
margin: _margin,
onEnter: _mouseIsConnected ? (_) => _handleMouseEnter() : null,
onExit: _mouseIsConnected ? (_) => _handleMouseExit() : null,
decoration: decoration,
textStyle: textStyle,
decoration: _decoration,
textStyle: _textStyle,
animation: CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
),
target: target,
verticalOffset: verticalOffset,
preferBelow: preferBelow,
verticalOffset: _verticalOffset,
preferBelow: _preferBelow,
),
);
_entry = OverlayEntry(builder: (BuildContext context) => overlay);
......@@ -632,8 +636,8 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
void _handlePress() {
_pressActivated = true;
final bool tooltipCreated = ensureTooltipVisible();
if (tooltipCreated && enableFeedback) {
if (triggerMode == TooltipTriggerMode.longPress)
if (tooltipCreated && _enableFeedback) {
if (_triggerMode == TooltipTriggerMode.longPress)
Feedback.forLongPress(context);
else
Feedback.forTap(context);
......@@ -673,22 +677,22 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
);
}
height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight();
padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding();
margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin;
verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset;
preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow;
excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration;
textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle;
waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration;
showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration;
hoverShowDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultHoverShowDuration;
triggerMode = widget.triggerMode ?? tooltipTheme.triggerMode ?? _defaultTriggerMode;
enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback;
_height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight();
_padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding();
_margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin;
_verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset;
_preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow;
_excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics;
_decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration;
_textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle;
_waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration;
_showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration;
_hoverShowDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultHoverShowDuration;
_triggerMode = widget.triggerMode ?? tooltipTheme.triggerMode ?? _defaultTriggerMode;
_enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback;
Widget result = Semantics(
label: excludeFromSemantics
label: _excludeFromSemantics
? null
: _tooltipMessage,
child: widget.child,
......@@ -698,9 +702,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
if (_visible) {
result = GestureDetector(
behavior: HitTestBehavior.opaque,
onLongPress: (triggerMode == TooltipTriggerMode.longPress) ?
onLongPress: (_triggerMode == TooltipTriggerMode.longPress) ?
_handlePress : null,
onTap: (triggerMode == TooltipTriggerMode.tap) ? _handlePress : null,
onTap: (_triggerMode == TooltipTriggerMode.tap) ? _handlePress : null,
excludeFromSemantics: true,
child: result,
);
......
......@@ -9,23 +9,6 @@ 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() {
......@@ -162,13 +145,13 @@ void main() {
});
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(
MaterialApp(
home: TooltipVisibility(
visible: false,
child: Tooltip(
key: key,
key: tooltipKey,
message: tooltipText,
child: const SizedBox(width: 100.0, height: 100.0),
),
......@@ -176,19 +159,19 @@ void main() {
),
);
_ensureTooltipVisible(key);
tooltipKey.currentState?.ensureTooltipVisible();
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();
final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>();
await tester.pumpWidget(
MaterialApp(
home: TooltipVisibility(
visible: true,
child: Tooltip(
key: key,
key: tooltipKey,
message: tooltipText,
child: const SizedBox(width: 100.0, height: 100.0),
),
......@@ -196,7 +179,7 @@ void main() {
),
);
_ensureTooltipVisible(key);
tooltipKey.currentState?.ensureTooltipVisible();
await tester.pump();
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