diff --git a/README.md b/README.md index 934ce29..e008b91 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,12 @@ Check example project for usage and/or clarifications.
+## Close tooltip by clicking outside + +```dart +OverlayTooltipItem( + dismissOnTap: true, +``` ## Getting Started diff --git a/example/lib/main.dart b/example/lib/main.dart index 524c73b..d9a5a0b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -54,6 +54,7 @@ class _MySamplePageState extends State { Widget build(BuildContext context) { return OverlayTooltipScaffold( // overlayColor: Colors.red.withOpacity(.4), + height: 1800, tooltipAnimationCurve: Curves.linear, tooltipAnimationDuration: const Duration(milliseconds: 1000), controller: _controller, @@ -61,6 +62,7 @@ class _MySamplePageState extends State { await Future.delayed(const Duration(milliseconds: 500)); return initializedWidgetLength == 3 && !done; }, + dismissOnTap: true, preferredOverlay: GestureDetector( onTap: () { _controller.dismiss(); @@ -72,87 +74,90 @@ class _MySamplePageState extends State { color: Colors.blue.withOpacity(.2), ), ), - builder: (context) => Scaffold( - backgroundColor: Colors.white, - appBar: AppBar( - toolbarHeight: 70, + builder: (context) { + return Scaffold( backgroundColor: Colors.white, - elevation: 0, - actions: [ - OverlayTooltipItem( - displayIndex: 1, - tooltip: (controller) => Padding( - padding: const EdgeInsets.only(right: 15), - child: MTooltip(title: 'Button', controller: controller), - ), - child: Center( - child: Container( - width: 40, - height: 40, - margin: const EdgeInsets.only(right: 15), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - gradient: const LinearGradient( - colors: [Colors.orange, Colors.deepOrange])), - child: const Icon( - Icons.add, - color: Colors.white, + appBar: AppBar( + toolbarHeight: 70, + backgroundColor: Colors.white, + elevation: 0, + actions: [ + OverlayTooltipItem( + displayIndex: 1, + tooltip: (controller) => Padding( + padding: const EdgeInsets.only(right: 15), + child: MTooltip(title: 'Button', controller: controller), + ), + child: Center( + child: Container( + width: 40, + height: 40, + margin: const EdgeInsets.only(right: 15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + gradient: const LinearGradient( + colors: [Colors.orange, Colors.deepOrange])), + child: const Icon( + Icons.add, + color: Colors.white, + ), + alignment: Alignment.center, ), - alignment: Alignment.center, ), - ), - ) - ], - ), - floatingActionButton: OverlayTooltipItem( - displayIndex: 2, - tooltip: (controller) => Padding( - padding: const EdgeInsets.only(bottom: 15), - child: MTooltip(title: 'Floating button', controller: controller), + ) + ], ), - tooltipVerticalPosition: TooltipVerticalPosition.TOP, - child: FloatingActionButton( - backgroundColor: Colors.purple, - onPressed: () {}, - child: const Icon(Icons.message), + floatingActionButton: OverlayTooltipItem( + displayIndex: 2, + tooltip: (controller) => Padding( + padding: const EdgeInsets.only(bottom: 15), + child: MTooltip(title: 'Floating button', controller: controller), + ), + tooltipVerticalPosition: TooltipVerticalPosition.TOP, + child: FloatingActionButton( + backgroundColor: Colors.purple, + onPressed: () {}, + child: const Icon(Icons.message), + ), ), - ), - body: ListView( - children: [ - ...[ - _sampleWidget(), - OverlayTooltipItem( - displayIndex: 0, - tooltip: (controller) => Padding( - padding: const EdgeInsets.only(left: 15), - child: MTooltip( - title: 'Text Tile', controller: controller), - ), - child: _sampleWidget()), - _sampleWidget(), + body: ListView( + children: [ + ...[ + _sampleWidget(), + OverlayTooltipItem( + displayIndex: 0, + tooltip: (controller) => Padding( + padding: const EdgeInsets.only(left: 15), + child: MTooltip( + title: 'Text Tile', controller: controller), + ), + child: _sampleWidget()), + _sampleWidget(), + ], + TextButton( + onPressed: () { + setState(() { + done = false; + }); + }, + child: const Text('reset Tooltip')), + TextButton( + onPressed: () { + //_controller.start(); + OverlayTooltipScaffold.of(context)?.controller.start(); + }, + child: const Text('Start Tooltip manually')), + TextButton( + onPressed: () { + //_controller.start(1); + OverlayTooltipScaffold.of(context)?.controller.start(1); + }, + child: const Text('Start at second item')), + for (var i = 0; i < 11; i++) _sampleWidget(), ], - TextButton( - onPressed: () { - setState(() { - done = false; - }); - }, - child: const Text('reset Tooltip')), - TextButton( - onPressed: () { - //_controller.start(); - OverlayTooltipScaffold.of(context)?.controller.start(); - }, - child: const Text('Start Tooltip manually')), - TextButton( - onPressed: () { - //_controller.start(1); - OverlayTooltipScaffold.of(context)?.controller.start(1); - }, - child: const Text('Start at second item')), - ], - ), - ), + ), + ); + }, ); } diff --git a/lib/src/core/overlay_tooltip_item.dart b/lib/src/core/overlay_tooltip_item.dart index e405949..ac49b35 100644 --- a/lib/src/core/overlay_tooltip_item.dart +++ b/lib/src/core/overlay_tooltip_item.dart @@ -43,6 +43,9 @@ class _OverlayTooltipItemImplState extends State { void _addToPlayableWidget() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { try { + if (!mounted) { + return; + } OverlayTooltipScaffold.of(context)?.addPlayableWidget( OverlayTooltipModel( absorbPointer: widget.absorbPointer, diff --git a/lib/src/core/overlay_tooltip_scaffold.dart b/lib/src/core/overlay_tooltip_scaffold.dart index b61ac99..08b5658 100644 --- a/lib/src/core/overlay_tooltip_scaffold.dart +++ b/lib/src/core/overlay_tooltip_scaffold.dart @@ -13,7 +13,9 @@ abstract class OverlayTooltipScaffoldImpl extends StatefulWidget { final Color overlayColor; final Duration tooltipAnimationDuration; final Curve tooltipAnimationCurve; + final bool dismissOnTap; final Widget? preferredOverlay; + final double? height; OverlayTooltipScaffoldImpl({ Key? key, @@ -23,7 +25,11 @@ abstract class OverlayTooltipScaffoldImpl extends StatefulWidget { required this.startWhen, required this.tooltipAnimationDuration, required this.tooltipAnimationCurve, + + /// If true, the tooltip will be dismissed when the user taps on the screen at any area exclude tooltip. + this.dismissOnTap = false, this.preferredOverlay, + this.height, }) : super(key: key) { if (startWhen != null) controller.setStartWhen(startWhen!); } @@ -45,54 +51,83 @@ class OverlayTooltipScaffoldImplState Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, - body: Stack( - fit: StackFit.expand, - children: [ - Positioned.fill(child: Builder(builder: (context) { - return widget.builder(context); - })), - StreamBuilder( - stream: widget.controller.widgetsPlayStream, - builder: (context, snapshot) { - return snapshot.data == null || - snapshot.data!.widgetKey.globalPaintBounds == null - ? SizedBox.shrink() - : Positioned.fill( - child: Stack( - children: [ - widget.preferredOverlay ?? - Container( - height: double.infinity, - width: double.infinity, - color: widget.overlayColor, - ), - TweenAnimationBuilder( - key: ValueKey(snapshot.data!.displayIndex), - tween: Tween(begin: 0, end: 1), - duration: widget.tooltipAnimationDuration, - curve: widget.tooltipAnimationCurve, - builder: (_, double val, child) { - val = min(val, 1); - val = max(val, 0); - return Opacity( - opacity: val, - child: child, - ); - }, - child: _TooltipLayout( - model: snapshot.data!, - controller: widget.controller, - ), - ), - ], - ), - ); - }, - ) - ], + body: StreamBuilder( + stream: widget.controller.widgetsPlayStream, + builder: (context, snapshot) { + final show = snapshot.data == null || + snapshot.data!.widgetKey.globalPaintBounds == null; + if (widget.height == null) { + return _stackBody(show, snapshot); + } + final baseHeight = MediaQuery.of(context).size.height; + final height = show ? baseHeight : (widget.height ?? baseHeight); + return SingleChildScrollView( + physics: show ? NeverScrollableScrollPhysics() : null, + child: SizedBox( + height: height, + child: _stackBody(show, snapshot), + ), + ); + }, ), ); } + + Stack _stackBody(bool show, AsyncSnapshot snapshot) { + return Stack( + key: ValueKey('OverlayTooltipScaffoldStack'), + fit: StackFit.expand, + children: [ + Positioned.fill(child: Builder(builder: (context) { + return widget.builder(context); + })), + show + ? SizedBox.shrink() + : Container( + child: widget.dismissOnTap + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + widget.controller.dismiss(); + }, + child: _bodyToolTip(snapshot), + ) + : _bodyToolTip(snapshot), + ), + ], + ); + } + + Widget _bodyToolTip(AsyncSnapshot snapshot) { + return Stack( + children: [ + widget.preferredOverlay ?? + Container( + height: double.infinity, + width: double.infinity, + color: widget.overlayColor, + ), + TweenAnimationBuilder( + key: ValueKey(snapshot.data!.displayIndex), + tween: Tween(begin: 0, end: 1), + duration: widget.tooltipAnimationDuration, + curve: widget.tooltipAnimationCurve, + builder: (_, double val, child) { + val = min(val, 1); + val = max(val, 0); + return Opacity( + opacity: val, + child: child, + ); + }, + child: _TooltipLayout( + model: snapshot.data!, + controller: widget.controller, + ), + ), + ], + ); + } } class _TooltipLayout extends StatelessWidget { diff --git a/lib/src/impl.dart b/lib/src/impl.dart index 7090ae7..7889657 100644 --- a/lib/src/impl.dart +++ b/lib/src/impl.dart @@ -21,7 +21,10 @@ class OverlayTooltipScaffold extends OverlayTooltipScaffoldImpl { final Duration tooltipAnimationDuration; final Curve tooltipAnimationCurve; + final double? height; + /// If true, the tooltip will be dismissed when the user taps on the screen at any area exclude tooltip. + final bool dismissOnTap; // Set a preferred overlay widget. // This can be useful for gesture detection on your custom overlays final Widget? preferredOverlay; @@ -34,7 +37,14 @@ class OverlayTooltipScaffold extends OverlayTooltipScaffoldImpl { this.startWhen, this.tooltipAnimationDuration = const Duration(milliseconds: 500), this.tooltipAnimationCurve = Curves.decelerate, + + /// If true, the tooltip will be dismissed when the user taps on the screen at any area exclude tooltip. + this.dismissOnTap = false, this.preferredOverlay, + + /// The height of the overlay, if null, the overlay will take the height of the screen + /// Need for show big tooltip on small screen + this.height, }) : super( key: key, controller: controller, @@ -43,7 +53,9 @@ class OverlayTooltipScaffold extends OverlayTooltipScaffoldImpl { startWhen: startWhen, tooltipAnimationDuration: tooltipAnimationDuration, tooltipAnimationCurve: tooltipAnimationCurve, + dismissOnTap: dismissOnTap, preferredOverlay: preferredOverlay, + height: height, ); static OverlayTooltipScaffoldImplState? of(BuildContext context) {