diff --git a/example/lib/main.dart b/example/lib/main.dart index a4323db0..31b75979 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -190,6 +190,31 @@ 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( + horizontal: 8, + vertical: 5, + ), + ), ), const SizedBox( width: 10, @@ -234,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, @@ -295,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), + ), ), ); } @@ -313,27 +371,45 @@ 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, horizontal: 8), + ), + ), ), ); } 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 11ad65b1..4834cafd 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; + const Showcase({ required this.key, required this.child, @@ -273,6 +277,7 @@ class Showcase extends StatefulWidget { this.tooltipPosition, this.titlePadding, this.descriptionPadding, + this.toolTipAction, }) : height = null, width = null, container = null, @@ -327,6 +332,7 @@ class Showcase extends StatefulWidget { tooltipPadding = const EdgeInsets.symmetric(vertical: 8), titlePadding = null, descriptionPadding = null, + toolTipAction = null, assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0, "overlay opacity must be between 0 and 1."); @@ -552,6 +558,7 @@ class _ShowcaseState extends State { tooltipPosition: widget.tooltipPosition, titlePadding: widget.titlePadding, descriptionPadding: widget.descriptionPadding, + toolTipAction: widget.toolTipAction, ), ], ], 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 7ec91789..9a64684b 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; const ToolTipWidget({ Key? key, @@ -90,6 +92,7 @@ class ToolTipWidget extends StatefulWidget { this.tooltipPosition, this.titlePadding, this.descriptionPadding, + this.toolTipAction, }) : super(key: key); @override @@ -108,11 +111,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); @@ -126,10 +133,9 @@ 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); } void _getTooltipWidth() { @@ -304,7 +310,7 @@ class _ToolTipWidgetState extends State @override void didChangeDependencies() { - _getTooltipWidth(); + //_getTooltipWidth(); super.didChangeDependencies(); } @@ -350,8 +356,8 @@ 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 ?? @@ -368,67 +374,89 @@ 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: const SizedBox( - height: arrowHeight, - width: arrowWidth, + child: MeasureSize( + onSizeChange: onTooltipSizeChanged, + 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, + ), ), ), - ), - 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: 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( + 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, + style: widget.titleTextStyle ?? + Theme.of(context) + .textTheme + .titleLarge! + .merge( + TextStyle( + color: widget.textColor, + ), + ), + ), + ), Padding( - padding: widget.titlePadding ?? + padding: widget.descriptionPadding ?? EdgeInsets.zero, child: Text( - widget.title!, - textAlign: widget.titleAlignment, - style: widget.titleTextStyle ?? + widget.description!, + textAlign: widget.descriptionAlignment, + style: widget.descTextStyle ?? Theme.of(context) .textTheme - .titleLarge! + .titleSmall! .merge( TextStyle( color: widget.textColor, @@ -436,30 +464,20 @@ class _ToolTipWidgetState extends State ), ), ), - Padding( - padding: widget.descriptionPadding ?? - EdgeInsets.zero, - child: Text( - widget.description!, - textAlign: widget.descriptionAlignment, - style: widget.descTextStyle ?? - Theme.of(context) - .textTheme - .titleSmall! - .merge( - TextStyle( - color: widget.textColor, - ), - ), + SizedBox( + width: tooltipWidth == 0 + ? null + : tooltipWidth, + child: widget.toolTipAction, ), - ), - ], + ], + ), ), ), ), ), - ), - ], + ], + ), ), ), ), @@ -507,6 +525,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);