From f3dd441e2dc4402282749f5a52a4c5d0ca8e3527 Mon Sep 17 00:00:00 2001 From: Kravchenko Igor Date: Fri, 2 Jun 2023 15:48:39 +0600 Subject: [PATCH 1/4] feat: dismissOnTap --- README.md | 6 +++ lib/src/core/overlay_tooltip_scaffold.dart | 58 ++++++++++++++-------- lib/src/impl.dart | 38 ++++++++------ 3 files changed, 66 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index c456c9c..d8f9cbb 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,12 @@ Check example project for usage and/or clarifications.
+## Close tooltip by clicking outside + +```dart +OverlayTooltipItem( + dismissOnTap: true, +``` ## Getting Started diff --git a/lib/src/core/overlay_tooltip_scaffold.dart b/lib/src/core/overlay_tooltip_scaffold.dart index 5709f9f..0b4d9a5 100644 --- a/lib/src/core/overlay_tooltip_scaffold.dart +++ b/lib/src/core/overlay_tooltip_scaffold.dart @@ -13,6 +13,7 @@ abstract class OverlayTooltipScaffoldImpl extends StatefulWidget { final Color overlayColor; final Duration tooltipAnimationDuration; final Curve tooltipAnimationCurve; + final bool dismissOnTap; OverlayTooltipScaffoldImpl({ Key? key, @@ -22,6 +23,9 @@ 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, }) : super(key: key) { if (startWhen != null) controller.setStartWhen(startWhen!); } @@ -56,27 +60,15 @@ class OverlayTooltipScaffoldImplState snapshot.data!.widgetKey.globalPaintBounds == null ? SizedBox.shrink() : Positioned.fill( - child: Container( - color: widget.overlayColor, - child: 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, - ), - ), - ), + child: widget.dismissOnTap + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + widget.controller.dismiss(); + }, + child: _bodyToolTip(snapshot), + ) + : _bodyToolTip(snapshot), ); }, ) @@ -84,6 +76,30 @@ class OverlayTooltipScaffoldImplState ), ); } + + Container _bodyToolTip(AsyncSnapshot snapshot) { + return Container( + color: widget.overlayColor, + child: 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 5e8c6b1..60846c7 100644 --- a/lib/src/impl.dart +++ b/lib/src/impl.dart @@ -22,6 +22,9 @@ class OverlayTooltipScaffold extends OverlayTooltipScaffoldImpl { final Curve tooltipAnimationCurve; + /// If true, the tooltip will be dismissed when the user taps on the screen at any area exclude tooltip. + final bool dismissOnTap; + OverlayTooltipScaffold({ Key? key, required this.controller, @@ -30,14 +33,19 @@ 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, }) : super( - key: key, - controller: controller, - builder: builder, - overlayColor: overlayColor, - startWhen: startWhen, - tooltipAnimationDuration: tooltipAnimationDuration, - tooltipAnimationCurve: tooltipAnimationCurve); + key: key, + controller: controller, + builder: builder, + overlayColor: overlayColor, + startWhen: startWhen, + tooltipAnimationDuration: tooltipAnimationDuration, + tooltipAnimationCurve: tooltipAnimationCurve, + dismissOnTap: dismissOnTap, + ); static OverlayTooltipScaffoldImplState? of(BuildContext context) { final OverlayTooltipScaffoldImplState? result = @@ -76,14 +84,14 @@ class OverlayTooltipItem extends OverlayTooltipItemImpl { /// This determines the order of display when overlay is started final int displayIndex; - OverlayTooltipItem( - {Key? key, - required this.displayIndex, - required this.child, - required this.tooltip, - this.tooltipVerticalPosition = TooltipVerticalPosition.BOTTOM, - this.tooltipHorizontalPosition = TooltipHorizontalPosition.WITH_WIDGET}) - : super( + OverlayTooltipItem({ + Key? key, + required this.displayIndex, + required this.child, + required this.tooltip, + this.tooltipVerticalPosition = TooltipVerticalPosition.BOTTOM, + this.tooltipHorizontalPosition = TooltipHorizontalPosition.WITH_WIDGET, + }) : super( key: key, child: child, displayIndex: displayIndex, From fd11c1381545131a503a01b2a6a665e2b54bf46c Mon Sep 17 00:00:00 2001 From: Kravchenko Igor Date: Tue, 6 Jun 2023 13:49:57 +0600 Subject: [PATCH 2/4] fix: mounted --- lib/src/core/overlay_tooltip_item.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/core/overlay_tooltip_item.dart b/lib/src/core/overlay_tooltip_item.dart index cd8ec9c..9db5334 100644 --- a/lib/src/core/overlay_tooltip_item.dart +++ b/lib/src/core/overlay_tooltip_item.dart @@ -41,6 +41,9 @@ class _OverlayTooltipItemImplState extends State { void _addToPlayableWidget() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { try { + if (!mounted) { + return; + } OverlayTooltipScaffold.of(context)?.addPlayableWidget( OverlayTooltipModel( child: widget.child, From 179d962ac482a9fc997dcbfd6491da3f01ec24be Mon Sep 17 00:00:00 2001 From: Kravchenko Igor Date: Mon, 12 Jun 2023 13:36:52 +0600 Subject: [PATCH 3/4] feat: scroll for overlay view --- example/lib/main.dart | 163 +++++++++++---------- lib/src/core/overlay_tooltip_scaffold.dart | 67 +++++---- lib/src/impl.dart | 6 + 3 files changed, 131 insertions(+), 105 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 8d13876..a47e0a7 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: () { //move the overlay forward or backwards @@ -71,87 +73,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')), - ], - ), - ), + ), + ); + }, ); } @@ -164,19 +169,19 @@ class _MySamplePageState extends State { borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.all(15), margin: const EdgeInsets.fromLTRB(15, 0, 15, 15), - child: Column( + child: const Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Lorem Ipsum is simply dummy text of the printing and' + Text('Lorem Ipsum is simply dummy text of the printing and' 'industry. Lorem Ipsum has been the industry\'s' 'standard dummy text ever since the 1500s'), - const SizedBox( + SizedBox( height: 10, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ + children: [ Icon(Icons.bookmark_border), Icon(Icons.delete_outline_sharp) ], diff --git a/lib/src/core/overlay_tooltip_scaffold.dart b/lib/src/core/overlay_tooltip_scaffold.dart index 3c0aec4..56f9288 100644 --- a/lib/src/core/overlay_tooltip_scaffold.dart +++ b/lib/src/core/overlay_tooltip_scaffold.dart @@ -15,6 +15,7 @@ abstract class OverlayTooltipScaffoldImpl extends StatefulWidget { final Curve tooltipAnimationCurve; final bool dismissOnTap; final Widget? preferredOverlay; + final double? height; OverlayTooltipScaffoldImpl({ Key? key, @@ -28,6 +29,7 @@ abstract class OverlayTooltipScaffoldImpl extends StatefulWidget { /// 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!); } @@ -49,36 +51,49 @@ 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: widget.dismissOnTap - ? GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - widget.controller.dismiss(); - }, - child: _bodyToolTip(snapshot), - ) - : _bodyToolTip(snapshot), - ); - }, - ) - ], + body: StreamBuilder( + stream: widget.controller.widgetsPlayStream, + builder: (context, snapshot) { + final show = snapshot.data == null || + snapshot.data!.widgetKey.globalPaintBounds == null; + final baseHeight = MediaQuery.of(context).size.height; + final height = show ? baseHeight : (widget.height ?? baseHeight); + return SingleChildScrollView( + 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: [ diff --git a/lib/src/impl.dart b/lib/src/impl.dart index cd9ccf2..b9522bf 100644 --- a/lib/src/impl.dart +++ b/lib/src/impl.dart @@ -21,6 +21,7 @@ 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; @@ -40,6 +41,10 @@ class OverlayTooltipScaffold extends OverlayTooltipScaffoldImpl { /// 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, @@ -50,6 +55,7 @@ class OverlayTooltipScaffold extends OverlayTooltipScaffoldImpl { tooltipAnimationCurve: tooltipAnimationCurve, dismissOnTap: dismissOnTap, preferredOverlay: preferredOverlay, + height: height, ); static OverlayTooltipScaffoldImplState? of(BuildContext context) { From ff6b02577a8552ce5322b7c711b0a2bcdf4245b7 Mon Sep 17 00:00:00 2001 From: Kravchenko Igor Date: Fri, 30 Jun 2023 23:25:56 +0600 Subject: [PATCH 4/4] fix: scroll problems --- lib/src/core/overlay_tooltip_scaffold.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/core/overlay_tooltip_scaffold.dart b/lib/src/core/overlay_tooltip_scaffold.dart index 56f9288..807a727 100644 --- a/lib/src/core/overlay_tooltip_scaffold.dart +++ b/lib/src/core/overlay_tooltip_scaffold.dart @@ -56,9 +56,13 @@ class OverlayTooltipScaffoldImplState 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),