From a7c83fff5829e3c46fb204c0850e719f14c93740 Mon Sep 17 00:00:00 2001 From: faiyaz Date: Wed, 15 Mar 2023 18:33:27 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Tooltip=20action=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/main.dart | 113 ++++++++++++++--- lib/showcaseview.dart | 1 + lib/src/showcase.dart | 7 ++ lib/src/tooltip_action.dart | 102 +++++++++++++++ lib/src/tooltip_widget.dart | 241 ++++++++++++++++++++---------------- 5 files changed, 340 insertions(+), 124 deletions(-) create mode 100644 lib/src/tooltip_action.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 9abcaf5d..7c9d1971 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -192,6 +192,29 @@ class _MailPageState extends State { color: Theme.of(context).primaryColor, ), ), + toolTipAction: ToolTipAction( + actionOne: ActionWidgetConfig( + actionTitle: 'Prev', + onActionTap: + ShowCaseWidget.of(context) + .previous, + ), + actionTwo: ActionWidgetConfig( + actionTitle: 'Next', + decoration: BoxDecoration( + color: + Theme.of(context).primaryColor, + borderRadius: + BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(10), + onActionTap: + ShowCaseWidget.of(context).next, + ), + actionPadding: + const EdgeInsets.symmetric( + vertical: 5), + ), ), const SizedBox( width: 10, @@ -236,6 +259,23 @@ class _MailPageState extends State { ), child: Image.asset('assets/simform.png'), ), + toolTipAction: ToolTipAction( + actionOne: ActionWidgetConfig( + actionTitle: 'Prev', + onActionTap: ShowCaseWidget.of(context).previous, + textStyle: const TextStyle(color: Colors.white), + ), + actionTwo: ActionWidgetConfig( + actionTitle: 'Next', + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(10), + onActionTap: ShowCaseWidget.of(context).next, + ), + actionPadding: const EdgeInsets.symmetric(vertical: 5), + ), ), const SizedBox( width: 12, @@ -297,6 +337,22 @@ class _MailPageState extends State { Icons.add, ), ), + toolTipAction: ToolTipAction( + actionOne: ActionWidgetConfig( + actionTitle: 'Prev', + onActionTap: ShowCaseWidget.of(context).previous, + ), + actionTwo: ActionWidgetConfig( + actionTitle: 'Next', + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(10), + onActionTap: ShowCaseWidget.of(context).next, + ), + actionPadding: const EdgeInsets.symmetric(vertical: 5), + ), ), ); } @@ -315,27 +371,44 @@ class _MailPageState extends State { child: Container( padding: const EdgeInsets.symmetric(vertical: 8), child: Showcase( - key: key, - description: 'Tap to check mail', - tooltipPosition: TooltipPosition.top, - disposeOnTap: true, - onTargetClick: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const Detail(), - ), - ).then((_) { - setState(() { - ShowCaseWidget.of(context).startShowCase([_four, _five]); - }); + key: key, + description: 'Tap to check mail', + tooltipPosition: TooltipPosition.top, + disposeOnTap: true, + onTargetClick: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const Detail(), + ), + ).then((_) { + setState(() { + ShowCaseWidget.of(context).startShowCase([_four, _five]); }); - }, - child: MailTile( - mail: mail, - showCaseKey: _four, - showCaseDetail: showCaseDetail, - )), + }); + }, + child: MailTile( + mail: mail, + showCaseKey: _four, + showCaseDetail: showCaseDetail, + ), + toolTipAction: ToolTipAction( + actionOne: ActionWidgetConfig( + actionTitle: 'Prev', + onActionTap: ShowCaseWidget.of(context).previous, + ), + actionTwo: ActionWidgetConfig( + actionTitle: 'Next', + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.all(10), + onActionTap: ShowCaseWidget.of(context).next, + ), + actionPadding: const EdgeInsets.symmetric(vertical: 5), + ), + ), ), ); } diff --git a/lib/showcaseview.dart b/lib/showcaseview.dart index 1c6a0cc0..7ffd2f13 100644 --- a/lib/showcaseview.dart +++ b/lib/showcaseview.dart @@ -25,3 +25,4 @@ library showcaseview; export 'src/enum.dart'; export 'src/showcase.dart'; export 'src/showcase_widget.dart'; +export 'src/tooltip_action.dart'; diff --git a/lib/src/showcase.dart b/lib/src/showcase.dart index ee574a07..a843ceae 100644 --- a/lib/src/showcase.dart +++ b/lib/src/showcase.dart @@ -32,6 +32,7 @@ import 'get_position.dart'; import 'layout_overlays.dart'; import 'shape_clipper.dart'; import 'showcase_widget.dart'; +import 'tooltip_action.dart'; import 'tooltip_widget.dart'; class Showcase extends StatefulWidget { @@ -232,6 +233,9 @@ class Showcase extends StatefulWidget { /// Provides padding around the description. Default padding is zero. final EdgeInsets? descriptionPadding; + /// Provides tooltip action + final ToolTipAction? toolTipAction; + /// Provides text direction of tooltip title. final TextDirection? titleTextDirection; @@ -285,6 +289,7 @@ class Showcase extends StatefulWidget { this.tooltipPosition, this.titlePadding, this.descriptionPadding, + this.toolTipAction, this.titleTextDirection, this.descriptionTextDirection, this.onBarrierClick, @@ -343,6 +348,7 @@ class Showcase extends StatefulWidget { tooltipPadding = const EdgeInsets.symmetric(vertical: 8), titlePadding = null, descriptionPadding = null, + toolTipAction = null, titleTextDirection = null, descriptionTextDirection = null, assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0, @@ -571,6 +577,7 @@ class _ShowcaseState extends State { tooltipPosition: widget.tooltipPosition, titlePadding: widget.titlePadding, descriptionPadding: widget.descriptionPadding, + toolTipAction: widget.toolTipAction, titleTextDirection: widget.titleTextDirection, descriptionTextDirection: widget.descriptionTextDirection, ), diff --git a/lib/src/tooltip_action.dart b/lib/src/tooltip_action.dart new file mode 100644 index 00000000..c083f420 --- /dev/null +++ b/lib/src/tooltip_action.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; + +class ToolTipAction extends StatelessWidget { + const ToolTipAction({ + Key? key, + this.actionOne, + this.actionTwo, + this.actionPadding, + this.alignment = WrapAlignment.spaceBetween, + this.crossAxisAlignment = WrapCrossAlignment.center, + }) : actions = null, + super(key: key); + + const ToolTipAction.customAction({ + Key? key, + this.actions, + this.actionPadding, + this.alignment = WrapAlignment.spaceBetween, + this.crossAxisAlignment = WrapCrossAlignment.center, + }) : actionOne = null, + actionTwo = null, + super(key: key); + + /// Define configuration of first action + final ActionWidgetConfig? actionOne; + + /// Define configuration of second action + final ActionWidgetConfig? actionTwo; + + /// Define padding of action widget + final EdgeInsets? actionPadding; + + /// Define configuration of list of action + final List? actions; + + /// Define alignment of actions + final WrapAlignment alignment; + + /// Define crossAxisAlignment of actions + final WrapCrossAlignment crossAxisAlignment; + + @override + Widget build(BuildContext context) { + return Padding( + padding: actionPadding ?? EdgeInsets.zero, + child: Wrap( + alignment: alignment, + crossAxisAlignment: crossAxisAlignment, + children: actions != null + ? [for (final action in actions!) getActionWidget(action)] + : [getActionWidget(actionOne), getActionWidget(actionTwo)], + ), + ); + } + + Widget getActionWidget(ActionWidgetConfig? actionWidgetConfig) { + if (actionWidgetConfig == null) return const SizedBox(); + return GestureDetector( + onTap: actionWidgetConfig.onActionTap, + child: Container( + decoration: actionWidgetConfig.decoration, + padding: actionWidgetConfig.padding, + margin: actionWidgetConfig.margin, + child: Text( + actionWidgetConfig.actionTitle, + style: actionWidgetConfig.textStyle, + maxLines: 1, + ), + ), + ); + } +} + +class ActionWidgetConfig { + const ActionWidgetConfig({ + Key? key, + required this.actionTitle, + this.decoration, + this.padding, + this.margin, + this.textStyle, + required this.onActionTap, + }); + + /// Defines title of action + final String actionTitle; + + /// Defines decoration of action + final BoxDecoration? decoration; + + /// Provide padding to the action + final EdgeInsets? padding; + + /// Provide margin to the action + final EdgeInsets? margin; + + /// Define text style of action + final TextStyle? textStyle; + + /// Called when user tap on action + final VoidCallback onActionTap; +} diff --git a/lib/src/tooltip_widget.dart b/lib/src/tooltip_widget.dart index 3caff7e4..8c30cdb2 100644 --- a/lib/src/tooltip_widget.dart +++ b/lib/src/tooltip_widget.dart @@ -27,6 +27,7 @@ import 'package:flutter/material.dart'; import 'enum.dart'; import 'get_position.dart'; import 'measure_size.dart'; +import 'tooltip_action.dart'; const _kDefaultPaddingFromParent = 14.0; @@ -59,6 +60,7 @@ class ToolTipWidget extends StatefulWidget { final TooltipPosition? tooltipPosition; final EdgeInsets? titlePadding; final EdgeInsets? descriptionPadding; + final ToolTipAction? toolTipAction; final TextDirection? titleTextDirection; final TextDirection? descriptionTextDirection; @@ -92,6 +94,7 @@ class ToolTipWidget extends StatefulWidget { this.tooltipPosition, this.titlePadding, this.descriptionPadding, + this.toolTipAction, this.titleTextDirection, this.descriptionTextDirection, }) : super(key: key); @@ -112,11 +115,15 @@ class _ToolTipWidgetState extends State late final Animation _scaleAnimation; double tooltipWidth = 0; + double toolTipHeight = 0; double tooltipScreenEdgePadding = 20; double tooltipTextPadding = 15; TooltipPosition findPositionForContent(Offset position) { - var height = 120.0; + if (toolTipHeight == 0 || widget.tooltipPosition != null) { + return widget.tooltipPosition ?? TooltipPosition.bottom; + } + var height = toolTipHeight; height = widget.contentHeight ?? height; final bottomPosition = position.dy + ((widget.position?.getHeight() ?? 0) / 2); @@ -130,12 +137,12 @@ class _ToolTipWidgetState extends State viewInsets.bottom; final hasSpaceInBottom = (actualVisibleScreenHeight - bottomPosition) >= height; - return widget.tooltipPosition ?? - (hasSpaceInTop && !hasSpaceInBottom - ? TooltipPosition.top - : TooltipPosition.bottom); + return (hasSpaceInTop && !hasSpaceInBottom + ? TooltipPosition.top + : TooltipPosition.bottom); } + // ignore: unused_element void _getTooltipWidth() { final titleStyle = widget.titleTextStyle ?? Theme.of(context) @@ -306,12 +313,6 @@ class _ToolTipWidgetState extends State }); } - @override - void didChangeDependencies() { - _getTooltipWidth(); - super.didChangeDependencies(); - } - @override void dispose() { _movingAnimationController.dispose(); @@ -354,15 +355,17 @@ class _ToolTipWidgetState extends State if (widget.container == null) { return Positioned( top: contentY, - left: _getLeft(), - right: _getRight(), + left: tooltipWidth == 0 ? null : _getLeft(), + right: tooltipWidth == 0 ? null : _getRight(), child: ScaleTransition( scale: _scaleAnimation, alignment: widget.scaleAnimationAlignment ?? - Alignment( - _getAlignmentX(), - _getAlignmentY(), - ), + (tooltipWidth == 0 + ? Alignment.center + : Alignment( + _getAlignmentX(), + _getAlignmentY(), + )), child: FractionalTranslation( translation: Offset(0.0, contentFractionalOffset as double), child: SlideTransition( @@ -372,102 +375,120 @@ class _ToolTipWidgetState extends State ).animate(_movingAnimation), child: Material( type: MaterialType.transparency, - child: Container( - padding: widget.showArrow - ? EdgeInsets.only( - top: paddingTop - (isArrowUp ? arrowHeight : 0), - bottom: paddingBottom - (isArrowUp ? 0 : arrowHeight), - ) - : null, - child: Stack( - alignment: isArrowUp - ? Alignment.topLeft - : _getLeft() == null - ? Alignment.bottomRight - : Alignment.bottomLeft, - children: [ - if (widget.showArrow) - Positioned( - left: _getArrowLeft(arrowWidth), - right: _getArrowRight(arrowWidth), - child: CustomPaint( - painter: _Arrow( - strokeColor: widget.tooltipBackgroundColor!, - strokeWidth: 10, - paintingStyle: PaintingStyle.fill, - isUpArrow: isArrowUp, + child: MeasureSize( + onSizeChange: onTooltipSizeChanged, + child: AnimatedOpacity( + opacity: tooltipWidth == 0 ? 0 : 1, + duration: const Duration(microseconds: 1), + child: Container( + padding: widget.showArrow + ? EdgeInsets.only( + top: paddingTop - (isArrowUp ? arrowHeight : 0), + bottom: + paddingBottom - (isArrowUp ? 0 : arrowHeight), + ) + : null, + child: Stack( + alignment: isArrowUp + ? Alignment.topLeft + : _getLeft() == null + ? Alignment.bottomRight + : Alignment.bottomLeft, + children: [ + if (widget.showArrow) + Positioned( + left: _getArrowLeft(arrowWidth), + right: _getArrowRight(arrowWidth), + child: CustomPaint( + painter: _Arrow( + strokeColor: widget.tooltipBackgroundColor!, + strokeWidth: 10, + paintingStyle: PaintingStyle.fill, + isUpArrow: isArrowUp, + ), + child: const SizedBox( + height: arrowHeight, + width: arrowWidth, + ), + ), ), - child: const SizedBox( - height: arrowHeight, - width: arrowWidth, + Padding( + padding: EdgeInsets.only( + top: isArrowUp ? arrowHeight - 1 : 0, + bottom: isArrowUp ? 0 : arrowHeight - 1, ), - ), - ), - Padding( - padding: EdgeInsets.only( - top: isArrowUp ? arrowHeight - 1 : 0, - bottom: isArrowUp ? 0 : arrowHeight - 1, - ), - child: ClipRRect( - borderRadius: widget.tooltipBorderRadius ?? - BorderRadius.circular(8.0), - child: GestureDetector( - onTap: widget.onTooltipTap, - child: Container( - width: tooltipWidth, - padding: widget.tooltipPadding, - color: widget.tooltipBackgroundColor, - child: Column( - crossAxisAlignment: widget.title != null - ? CrossAxisAlignment.start - : CrossAxisAlignment.center, - children: [ - if (widget.title != null) - Padding( - padding: widget.titlePadding ?? - EdgeInsets.zero, - child: Text( - widget.title!, - textAlign: widget.titleAlignment, - textDirection: - widget.titleTextDirection, - style: widget.titleTextStyle ?? - Theme.of(context) - .textTheme - .titleLarge! - .merge( - TextStyle( - color: widget.textColor, + child: ClipRRect( + borderRadius: widget.tooltipBorderRadius ?? + BorderRadius.circular(8.0), + child: GestureDetector( + onTap: widget.onTooltipTap, + child: Container( + constraints: BoxConstraints( + maxWidth: widget.screenSize!.width), + width: + tooltipWidth == 0 ? null : tooltipWidth, + padding: widget.tooltipPadding, + color: widget.tooltipBackgroundColor, + child: Column( + crossAxisAlignment: widget.title != null + ? CrossAxisAlignment.start + : CrossAxisAlignment.center, + children: [ + if (widget.title != null) + Padding( + padding: widget.titlePadding ?? + EdgeInsets.zero, + child: Text( + widget.title!, + textAlign: widget.titleAlignment, + textDirection: + widget.titleTextDirection, + style: widget.titleTextStyle ?? + Theme.of(context) + .textTheme + .titleLarge! + .merge( + TextStyle( + color: widget.textColor, + ), + ), + ), + ), + Padding( + padding: widget.descriptionPadding ?? + EdgeInsets.zero, + child: Text( + widget.description!, + textAlign: + widget.descriptionAlignment, + textDirection: + widget.descriptionTextDirection, + style: widget.descTextStyle ?? + Theme.of(context) + .textTheme + .titleSmall! + .merge( + TextStyle( + color: widget.textColor, + ), ), - ), + ), ), - ), - Padding( - padding: widget.descriptionPadding ?? - EdgeInsets.zero, - child: Text( - widget.description!, - textAlign: widget.descriptionAlignment, - textDirection: - widget.descriptionTextDirection, - style: widget.descTextStyle ?? - Theme.of(context) - .textTheme - .titleSmall! - .merge( - TextStyle( - color: widget.textColor, - ), - ), - ), + SizedBox( + width: tooltipWidth == 0 + ? null + : tooltipWidth, + child: widget.toolTipAction, + ), + ], ), - ], + ), ), ), ), - ), + ], ), - ], + ), ), ), ), @@ -515,6 +536,18 @@ class _ToolTipWidgetState extends State ); } + void onTooltipSizeChanged(Size? size) { + if (size == null) return; + setState(() { + if (size.width > widget.screenSize!.width - tooltipScreenEdgePadding) { + tooltipWidth = widget.screenSize!.width - tooltipScreenEdgePadding; + } else { + tooltipWidth = size.width; + } + toolTipHeight = size.height; + }); + } + void onSizeChange(Size? size) { var tempPos = position; tempPos = Offset(position!.dx, position!.dy + size!.height);