From 24082122b5892e678789640fb3aa5beffc5de2d5 Mon Sep 17 00:00:00 2001 From: harsh-u-simform Date: Thu, 25 Jul 2024 20:56:01 +0530 Subject: [PATCH] feat: :sparkle: Provide tooltip action buttons --- CHANGELOG.md | 1 + README.md | 133 +++-- example/ios/Runner.xcodeproj/project.pbxproj | 500 ---------------- example/lib/main.dart | 202 +++---- lib/showcaseview.dart | 5 +- lib/src/default_tooltip_action.dart | 97 ---- lib/src/enum.dart | 27 +- lib/src/layout_overlays.dart | 4 +- lib/src/models/action_button_icon.dart | 17 + lib/src/models/tooltip_action_button.dart | 59 ++ lib/src/models/tooltip_action_config.dart | 39 ++ lib/src/showcase.dart | 61 +- lib/src/showcase_widget.dart | 10 + lib/src/tooltip_action_button.dart | 33 -- lib/src/tooltip_action_button_widget.dart | 78 +++ lib/src/tooltip_widget.dart | 573 ++++++++++++------- 16 files changed, 810 insertions(+), 1029 deletions(-) delete mode 100644 example/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 lib/src/default_tooltip_action.dart create mode 100644 lib/src/models/action_button_icon.dart create mode 100644 lib/src/models/tooltip_action_button.dart create mode 100644 lib/src/models/tooltip_action_config.dart delete mode 100644 lib/src/tooltip_action_button.dart create mode 100644 lib/src/tooltip_action_button_widget.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 45a853a6..0dc95435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Fixed [#449](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/449) - Null check operator used on a null value - [BREAKING] Improvement [#400](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/400) - remove Builder widget - Fixed [#435](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/435) - Extra padding when add targetShapeBorder +- Feature [#466](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/pull/466) - Provide tooltip action buttons ## [2.1.1] - Fixed [#425](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/425) - Unhandled breaking change in v2.1.0 diff --git a/README.md b/README.md index bd2941c2..e2e90345 100644 --- a/README.md +++ b/README.md @@ -130,55 +130,96 @@ WidgetsBinding.instance.addPostFrameCallback((_) => | onComplete | Function(int?, GlobalKey)? | | Triggered on completion of each showcase. | | onFinish | VoidCallback? | | Triggered when all the showcases are completed | | enableShowcase | bool | true | Enable or disable showcase globally. | +| globalTooltipActionConfig | TooltipActionConfig? | | Global tooltip actionbar config | +| globalTooltipActions | List? | | Global list of tooltip actions | ## Properties of `Showcase` and `Showcase.withWidget`: -| Name | Type | Default Behaviour | Description | `Showcase` | `ShowCaseWidget` | -|------------------------------|------------------|--------------------------------------------------|----------------------------------------------------------------------------------------------------|------------|------------------| -| key | GlobalKey | | Unique Global key for each showcase. | ✅ | ✅ | -| child | Widget | | The Target widget that you want to be showcased | ✅ | ✅ | -| title | String? | | Title of default tooltip | ✅ | | -| description | String? | | Description of default tooltip | ✅ | | -| container | Widget? | | Allows to create custom tooltip widget. | | ✅ | -| height | double? | | Height of custom tooltip widget | | ✅ | -| width | double? | | Width of custom tooltip widget | | ✅ | -| titleTextStyle | TextStyle? | | Text Style of title | ✅ | | -| descTextStyle | TextStyle? | | Text Style of description | ✅ | | -| titleAlignment | TextAlign | TextAlign.start | Alignment of title | ✅ | | -| descriptionAlignment | TextAlign | TextAlign.start | Alignment of description | ✅ | | -| targetShapeBorder | ShapeBorder | | If `targetBorderRadius` param is not provided then it applies shape border to target widget | ✅ | ✅ | -| targetBorderRadius | BorderRadius? | | Border radius of target widget | ✅ | ✅ | -| tooltipBorderRadius | BorderRadius? | BorderRadius.circular(8.0) | Border radius of tooltip | ✅ | | -| blurValue | double? | `ShowCaseWidget.blurValue` | Gaussian blur effect on overlay | ✅ | ✅ | -| tooltipPadding | EdgeInsets | EdgeInsets.symmetric(vertical: 8, horizontal: 8) | Padding to tooltip content | ✅ | | -| targetPadding | EdgeInsets | EdgeInsets.zero | Padding to target widget | ✅ | ✅ | -| overlayOpacity | double | 0.75 | Opacity of overlay layer | ✅ | ✅ | -| overlayColor | Color | Colors.black45 | Color of overlay layer | ✅ | ✅ | -| tooltipBackgroundColor | Color | Colors.white | Background Color of default tooltip | ✅ | | -| textColor | Color | Colors.black | Color of tooltip text | ✅ | | -| scrollLoadingWidget | Widget | | Loading widget on overlay until active showcase is visible to viewport when `autoScroll` is enable | ✅ | ✅ | -| movingAnimationDuration | Duration | Duration(milliseconds: 2000) | Duration of time this moving animation should last. | ✅ | ✅ | -| showArrow | bool | true | Shows tooltip with arrow | ✅ | | -| disableDefaultTargetGestures | bool | false | disable default gestures of target widget | ✅ | ✅ | -| disposeOnTap | bool? | false | Dismiss all showcases on target/tooltip tap | ✅ | ✅ | -| disableMovingAnimation | bool? | `ShowCaseWidget.disableMovingAnimation` | Disable bouncing/moving transition | ✅ | ✅ | -| disableScaleAnimation | bool? | `ShowCaseWidget.disableScaleAnimation` | Disable initial scale transition when showcase is being started and completed | ✅ | | -| scaleAnimationDuration | Duration | Duration(milliseconds: 300) | Duration of time scale animation should last. | ✅ | | -| scaleAnimationCurve | Curve | Curves.easeIn | Curve to use in scale animation. | ✅ | | -| scaleAnimationAlignment | Alignment? | | Origin of the coordinate in which the scale takes place, relative to the size of the box. | ✅ | | -| onToolTipClick | VoidCallback? | | Triggers when tooltip is being clicked. | ✅ | | -| onTargetClick | VoidCallback? | | Triggers when target widget is being clicked | ✅ | ✅ | -| onTargetDoubleTap | VoidCallback? | | Triggers when target widget is being double clicked | ✅ | ✅ | -| onTargetLongPress | VoidCallback? | | Triggers when target widget is being long pressed | ✅ | ✅ | -| onBarrierClick | VoidCallback? | | Triggers when barrier is clicked | ✅ | ✅ | -| tooltipPosition | TooltipPosition? | | Defines vertical position of tooltip respective to Target widget | ✅ | ✅ | -| titlePadding | EdgeInsets? | EdgeInsets.zero | Padding to title | ✅ | | -| descriptionPadding | EdgeInsets? | EdgeInsets.zero | Padding to description | ✅ | | -| titleTextDirection | TextDirection? | | Give textDirection to title | ✅ | | -| descriptionTextDirection | TextDirection? | | Give textDirection to description | ✅ | | -| descriptionTextDirection | TextDirection? | | Give textDirection to description | ✅ | | -| disableBarrierInteraction | bool | false | Disables barrier interaction for a particular showCase | ✅ | ✅ | -| toolTipSlideEndDistance | double | 7 | Defines motion range for tooltip slide animation | ✅ | ✅ | +| Name | Type | Default Behaviour | Description | `Showcase` | `ShowCaseWidget` | +|------------------------------|----------------------------|--------------------------------------------------|----------------------------------------------------------------------------------------------------|------------|-------------------| +| key | GlobalKey | | Unique Global key for each showcase. | ✅ | ✅ | +| child | Widget | | The Target widget that you want to be showcased | ✅ | ✅ | +| title | String? | | Title of default tooltip | ✅ | | +| description | String? | | Description of default tooltip | ✅ | | +| container | Widget? | | Allows to create custom tooltip widget. | | ✅ | +| height | double? | | Height of custom tooltip widget | | ✅ | +| width | double? | | Width of custom tooltip widget | | ✅ | +| titleTextStyle | TextStyle? | | Text Style of title | ✅ | | +| descTextStyle | TextStyle? | | Text Style of description | ✅ | | +| titleAlignment | TextAlign | TextAlign.start | Alignment of title | ✅ | | +| descriptionAlignment | TextAlign | TextAlign.start | Alignment of description | ✅ | | +| targetShapeBorder | ShapeBorder | | If `targetBorderRadius` param is not provided then it applies shape border to target widget | ✅ | ✅ | +| targetBorderRadius | BorderRadius? | | Border radius of target widget | ✅ | ✅ | +| tooltipBorderRadius | BorderRadius? | BorderRadius.circular(8.0) | Border radius of tooltip | ✅ | | +| blurValue | double? | `ShowCaseWidget.blurValue` | Gaussian blur effect on overlay | ✅ | ✅ | +| tooltipPadding | EdgeInsets | EdgeInsets.symmetric(vertical: 8, horizontal: 8) | Padding to tooltip content | ✅ | | +| targetPadding | EdgeInsets | EdgeInsets.zero | Padding to target widget | ✅ | ✅ | +| overlayOpacity | double | 0.75 | Opacity of overlay layer | ✅ | ✅ | +| overlayColor | Color | Colors.black45 | Color of overlay layer | ✅ | ✅ | +| tooltipBackgroundColor | Color | Colors.white | Background Color of default tooltip | ✅ | | +| textColor | Color | Colors.black | Color of tooltip text | ✅ | | +| scrollLoadingWidget | Widget | | Loading widget on overlay until active showcase is visible to viewport when `autoScroll` is enable | ✅ | ✅ | +| movingAnimationDuration | Duration | Duration(milliseconds: 2000) | Duration of time this moving animation should last. | ✅ | ✅ | +| showArrow | bool | true | Shows tooltip with arrow | ✅ | | +| disableDefaultTargetGestures | bool | false | disable default gestures of target widget | ✅ | ✅ | +| disposeOnTap | bool? | false | Dismiss all showcases on target/tooltip tap | ✅ | ✅ | +| disableMovingAnimation | bool? | `ShowCaseWidget.disableMovingAnimation` | Disable bouncing/moving transition | ✅ | ✅ | +| disableScaleAnimation | bool? | `ShowCaseWidget.disableScaleAnimation` | Disable initial scale transition when showcase is being started and completed | ✅ | | +| scaleAnimationDuration | Duration | Duration(milliseconds: 300) | Duration of time scale animation should last. | ✅ | | +| scaleAnimationCurve | Curve | Curves.easeIn | Curve to use in scale animation. | ✅ | | +| scaleAnimationAlignment | Alignment? | | Origin of the coordinate in which the scale takes place, relative to the size of the box. | ✅ | | +| onToolTipClick | VoidCallback? | | Triggers when tooltip is being clicked. | ✅ | | +| onTargetClick | VoidCallback? | | Triggers when target widget is being clicked | ✅ | ✅ | +| onTargetDoubleTap | VoidCallback? | | Triggers when target widget is being double clicked | ✅ | ✅ | +| onTargetLongPress | VoidCallback? | | Triggers when target widget is being long pressed | ✅ | ✅ | +| onBarrierClick | VoidCallback? | | Triggers when barrier is clicked | ✅ | ✅ | +| tooltipPosition | TooltipPosition? | | Defines vertical position of tooltip respective to Target widget | ✅ | ✅ | +| titlePadding | EdgeInsets? | EdgeInsets.zero | Padding to title | ✅ | | +| descriptionPadding | EdgeInsets? | EdgeInsets.zero | Padding to description | ✅ | | +| titleTextDirection | TextDirection? | | Give textDirection to title | ✅ | | +| descriptionTextDirection | TextDirection? | | Give textDirection to description | ✅ | | +| descriptionTextDirection | TextDirection? | | Give textDirection to description | ✅ | | +| disableBarrierInteraction | bool | false | Disables barrier interaction for a particular showCase | ✅ | ✅ | +| toolTipSlideEndDistance | double | 7 | Defines motion range for tooltip slide animation | ✅ | ✅ | +| tooltipActions | List? | [] | Provide a list of tooltip actions | ✅ | ✅ | +| tooltipActionConfig | TooltipActionConfig? | | Give configurations (alignment, position, etc...) to the tooltip actionbar | ✅ | ✅ | + +## Properties of `TooltipActionButton.withDefault` and `TooltipActionButton.custom`: + +| Name | Type | Default Behaviour | Description | `TooltipActionButton.withDefault` | `TooltipActionButton.custom` | +|---------------------------|---------------------|--------------------------------------------------|------------------------------------------------|-----------------------------------|------------------------------| +| button | Widget | | Provide custom tooltip action button widget | | ✅ | +| type | TooltipActionButton | | Type of action button (next, skip, previous) | ✅ | | +| backgroundColor | Color? | | Give background color to action button | ✅ | | +| borderRadius | BorderRadius? | BorderRadius.all(Radius.circular(50)) | Give border radius to action button | ✅ | | +| textStyle | TextStyle? | | Give text styles to the name of button | ✅ | | +| padding | EdgeInsets? | EdgeInsets.symmetric(horizontal: 15,vertical: 4) | Give padding to button content | ✅ | | +| leadIcon | ActionButtonIcon? | | Add icon at first before name in action button | ✅ | | +| tailIcon | ActionButtonIcon? | | Add icon at last after name in action button | ✅ | | +| name | String? | | Action button name | ✅ | | +| onTap | VoidCallback? | | Triggers when action button is tapped | ✅ | | +| borderWidth | double? | 0 | Give border width to action button | ✅ | | +| borderColor | Color? | Colors.white | Give color to the border of action button | ✅ | | +| shouldShowForFirstTooltip | bool? | true | Should show button on first tooltip | ✅ | | +| shouldShowForLastTooltip | bool? | true | Should show button on last tooltip | ✅ | | + +## Properties of `TooltipActionConfig`: + +| Name | Type | Default Behaviour | Description | +|----------------------------|-------------------------|:-------------------------------|-------------------------------------------------------------------| +| alignment | TooltipActionAlignment? | TooltipActionAlignment.left | Alignment of tooltip action buttons (left, right, center, spread) | +| actionGap | double? | 5 | Horizontal gap between the tooltip action buttons | +| padding | EdgeInsets? | EdgeInsets.zero | Padding to the tooltip actionbar | +| position | TooltipActionPosition? | TooltipActionPosition.inside | Position of tooltip actionbar (inside, outside) | +| gapBetweenContentAndAction | double? | 10 | Gap between tooltip content and actionbar | + +## Properties of `ActionButtonIcon.withIcon` and `ActionButtonIcon.withImageIcon`: + +| Name | Type | Default Behaviour | Description | `ActionButtonIcon.withIcon` | `ActionButtonIcon.withImageIcon` | +|---------|-------------|-------------------|--------------------------------------|-----------------------------|----------------------------------| +| icon | Icon | | Provide a icon to the button | ✅ | | +| icon | ImageIcon | | Provide a image icon to the button | | ✅ | +| padding | EdgeInsets? | | Give padding to the icon | ✅ | ✅ | ## How to use diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 18c726a3..00000000 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,500 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1300; - ORGANIZATIONNAME = "The Chromium Authors"; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 0910; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S8QB4VV633; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.simform.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.simform.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.simform.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/example/lib/main.dart b/example/lib/main.dart index 2a3d8ece..7d3a954b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -37,6 +37,10 @@ class MyApp extends StatelessWidget { blurValue: 1, autoPlayDelay: const Duration(seconds: 3), builder: (context) => const MailPage(), + globalTooltipActionConfig: const TooltipActionConfig( + position: TooltipActionPosition.inside, + alignment: TooltipActionAlignment.spread, + ), ), ), ); @@ -180,18 +184,17 @@ class _MailPageState extends State { description: 'Tap to see menu options', onBarrierClick: () => debugPrint('Barrier clicked'), - toolTipAction: DefaultToolTipAction( - color: Colors.red, - showCaseWidgetState: - ShowCaseWidget.of(context), - onBackPress: () => - debugPrint('Back Pressed!'), - onForwardPress: () => - debugPrint('Forward Pressed!'), + tooltipActionConfig: + const TooltipActionConfig( + alignment: TooltipActionAlignment.right, + position: TooltipActionPosition.outside, + gapBetweenContentAndAction: 10, ), - tooltipActionPosition: - TooltipActionPosition.inside, - showArrow: true, + tooltipActions: [ + TooltipActionButton.withDefault( + type: TooltipDefaultActionType.next, + ), + ], child: GestureDetector( onTap: () => debugPrint('menu button clicked'), @@ -234,33 +237,20 @@ class _MailPageState extends State { tooltipBackgroundColor: Theme.of(context).primaryColor, textColor: Colors.white, targetShapeBorder: const CircleBorder(), - toolTipAction: DefaultToolTipAction( - padding: const EdgeInsets.symmetric(vertical: 5), - color: Colors.white, - showCaseWidgetState: ShowCaseWidget.of(context), - back: const Padding( - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, - ), - child: Icon( - Icons.arrow_back, - color: Colors.white, - ), + tooltipActions: [ + TooltipActionButton.withDefault( + backgroundColor: Colors.transparent, + type: TooltipDefaultActionType.previous, + padding: EdgeInsets.zero, ), - forward: const Padding( - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 5, + TooltipActionButton.withDefault( + type: TooltipDefaultActionType.next, + backgroundColor: Colors.white, + textStyle: const TextStyle( + color: Colors.pinkAccent, ), - child: Icon( - Icons.arrow_forward, - color: Colors.white, - ), - ), - onBackPress: () => debugPrint('Back Pressed!'), - onForwardPress: () => debugPrint('Forward Pressed!'), - ), + ) + ], child: Container( padding: const EdgeInsets.all(5), width: 45, @@ -316,47 +306,39 @@ class _MailPageState extends State { title: 'Compose Mail', description: 'Click here to compose mail', targetShapeBorder: const CircleBorder(), - toolTipAction: Padding( - padding: const EdgeInsets.symmetric(vertical: 0), - child: Container( - padding: const EdgeInsets.all(10), - decoration: const BoxDecoration( - color: Colors.redAccent, - borderRadius: BorderRadius.all( - Radius.circular(10), + showArrow: false, + tooltipActionConfig: const TooltipActionConfig( + alignment: TooltipActionAlignment.spread, + actionGap: 15, + ), + tooltipActions: [ + TooltipActionButton.withDefault( + type: TooltipDefaultActionType.previous, + name: 'Back', + onTap: () { + // Write your code on button tap + ShowCaseWidget.of(context).previous(); + }, + backgroundColor: Colors.pink.shade50, + textStyle: const TextStyle( + color: Colors.pink, + )), + TooltipActionButton.withDefault( + type: TooltipDefaultActionType.next, + name: 'Close', + tailIcon: const ActionButtonIcon.withIcon( + icon: Icon( + Icons.close, + color: Colors.white, + size: 15, ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: ShowCaseWidget.of(context).previous, - child: const Icon( - Icons.arrow_back_ios_new, - color: Colors.white, - size: 17, - ), - ), - Text( - "${(ShowCaseWidget.of(context).activeWidgetId ?? 0) + 1} / ${ShowCaseWidget.of(context).ids?.length}", - style: const TextStyle( - color: Colors.white, - ), - ), - GestureDetector( - onTap: ShowCaseWidget.of(context).next, - child: const Icon( - Icons.arrow_forward_ios, - color: Colors.white, - size: 17, - ), - ), - ], - ), + onTap: () { + // Write your code on button tap + ShowCaseWidget.of(context).next(); + }, ), - ), - tooltipActionPosition: TooltipActionPosition.outsideTop, - showArrow: false, + ], child: FloatingActionButton( backgroundColor: Theme.of(context).primaryColor, onPressed: () { @@ -407,11 +389,6 @@ class _MailPageState extends State { }); }); }, - toolTipAction: DefaultToolTipAction( - color: Colors.white, - showCaseWidgetState: ShowCaseWidget.of(context), - ), - tooltipActionPosition: TooltipActionPosition.outsideTop, child: MailTile( mail: mail, showCaseKey: _four, @@ -502,41 +479,44 @@ class MailTile extends StatelessWidget { targetBorderRadius: const BorderRadius.all( Radius.circular(150), ), - container: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 45, - height: 45, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Color(0xffFCD8DC), - ), - child: Center( - child: Text( - 'S', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - fontSize: 16, + container: Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(15), + ), + ), + width: 140, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 45, + height: 45, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xffFCD8DC), + ), + child: Center( + child: Text( + 'S', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + fontSize: 16, + ), ), ), ), - ), - const SizedBox( - height: 10, - ), - const Text( - "Your sender's profile ", - style: TextStyle(color: Colors.white), - ) - ], - ), - toolTipAction: DefaultToolTipAction( - color: Colors.white, - showCaseWidgetState: ShowCaseWidget.of(context), - onBackPress: () => debugPrint('Back Pressed!'), - onForwardPress: () => debugPrint('Forward Pressed!'), + const SizedBox( + height: 10, + ), + const Text( + "Your sender's profile", + ) + ], + ), ), child: const SAvatarExampleChild(), ) diff --git a/lib/showcaseview.dart b/lib/showcaseview.dart index 223507e1..3da2f1cd 100644 --- a/lib/showcaseview.dart +++ b/lib/showcaseview.dart @@ -22,7 +22,10 @@ library showcaseview; -export 'src/default_tooltip_action.dart'; export 'src/enum.dart'; +export 'src/models/action_button_icon.dart'; +export 'src/models/tooltip_action_button.dart'; +export 'src/models/tooltip_action_config.dart'; export 'src/showcase.dart'; export 'src/showcase_widget.dart'; +export 'src/tooltip_action_button_widget.dart'; diff --git a/lib/src/default_tooltip_action.dart b/lib/src/default_tooltip_action.dart deleted file mode 100644 index 10d0d38c..00000000 --- a/lib/src/default_tooltip_action.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'showcase_widget.dart'; -import 'tooltip_action_button.dart'; - -/// Default Tooltip action Widget Nav -/// Shows tooltip navigation and index / count elements if the conditions are -/// indicated. -class DefaultToolTipAction extends StatelessWidget { - const DefaultToolTipAction({ - super.key, - this.color = Colors.black, - required this.showCaseWidgetState, - this.padding = EdgeInsets.zero, - this.textStyle, - this.iconSize, - this.back, - this.forward, - this.buttonColor, - this.onBackPress, - this.onForwardPress, - }); - - final Color color; - final ShowCaseWidgetState showCaseWidgetState; - final EdgeInsets padding; - final TextStyle? textStyle; - final double? iconSize; - final Widget? back; - final Widget? forward; - final Color? buttonColor; - final VoidCallback? onBackPress; - final VoidCallback? onForwardPress; - - @override - Widget build(BuildContext context) { - var ids = showCaseWidgetState.ids; - var activeWidgetId = showCaseWidgetState.activeWidgetId; - bool isFirstTip = activeWidgetId == 0; - bool isLastTip = activeWidgetId == (ids!.length - 1); - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (ids.isNotEmpty && activeWidgetId != null) ...[ - ToolTipActionButton( - action: isFirstTip - ? null - : () { - showCaseWidgetState.previous(); - onBackPress?.call(); - }, - padding: padding, - widget: back ?? - Icon( - Icons.keyboard_arrow_left, - color: buttonColor ?? color, - ), - opacity: isFirstTip ? 0.3 : 1, - ), - const SizedBox( - width: 4.0, - ), - Padding( - padding: padding, - child: Text( - "${activeWidgetId + 1} / ${ids.length}", - style: textStyle ?? - Theme.of(context).textTheme.bodyMedium?.copyWith( - color: color, - ), - ), - ), - const SizedBox( - width: 4.0, - ), - ToolTipActionButton( - action: isLastTip - ? null - : () { - showCaseWidgetState.next(); - onForwardPress?.call(); - }, - padding: padding, - widget: forward ?? - Icon( - Icons.keyboard_arrow_right, - color: buttonColor ?? color, - ), - opacity: isLastTip ? 0.3 : 1, - ) - ], - ], - ); - } -} diff --git a/lib/src/enum.dart b/lib/src/enum.dart index 00b024cb..5e3fcae3 100644 --- a/lib/src/enum.dart +++ b/lib/src/enum.dart @@ -20,6 +20,31 @@ * SOFTWARE. */ +import 'package:flutter/cupertino.dart'; + enum TooltipPosition { top, bottom } -enum TooltipActionPosition { outsideTop, outsideBottom, inside } +enum TooltipActionPosition { outside, inside } + +enum TooltipActionAlignment { + left(MainAxisAlignment.start), + right(MainAxisAlignment.end), + spread(MainAxisAlignment.spaceBetween), + center(MainAxisAlignment.center); + + const TooltipActionAlignment(this.alignment); + + final MainAxisAlignment alignment; +} + +enum TooltipDefaultActionType { + next(actionName: 'Next'), + skip(actionName: 'Skip'), + previous(actionName: 'Previous'); + + const TooltipDefaultActionType({ + required this.actionName, + }); + + final String actionName; +} diff --git a/lib/src/layout_overlays.dart b/lib/src/layout_overlays.dart index d8516b70..6c14f6fd 100644 --- a/lib/src/layout_overlays.dart +++ b/lib/src/layout_overlays.dart @@ -65,7 +65,9 @@ class AnchoredOverlay extends StatelessWidget { overlayBuilder: (overlayContext) { // To calculate the "anchor" point we grab the render box of // our parent Container and then we find the center of that box. - final box = context.findRenderObject() as RenderBox; + final box = context.findRenderObject() as RenderBox?; + + if (box == null) return const SizedBox.shrink(); final topLeft = box.size.topLeft( box.localToGlobal( diff --git a/lib/src/models/action_button_icon.dart b/lib/src/models/action_button_icon.dart new file mode 100644 index 00000000..da067045 --- /dev/null +++ b/lib/src/models/action_button_icon.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class ActionButtonIcon { + const ActionButtonIcon.withIcon({ + required this.icon, + this.padding, + }) : assert(icon is Icon, 'Icon must be of type Icon'); + + const ActionButtonIcon.withImageIcon({ + required this.icon, + this.padding, + }) : assert(icon is ImageIcon, 'Icon must be of type ImageIcon'); + + final Widget icon; + final EdgeInsets? padding; +} diff --git a/lib/src/models/tooltip_action_button.dart b/lib/src/models/tooltip_action_button.dart new file mode 100644 index 00000000..754afa96 --- /dev/null +++ b/lib/src/models/tooltip_action_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +import '../../showcaseview.dart'; + +class TooltipActionButton { + final Color? backgroundColor; + final BorderRadius? borderRadius; + final TextStyle? textStyle; + final EdgeInsets? padding; + final Widget? button; + final ActionButtonIcon? leadIcon; + final ActionButtonIcon? tailIcon; + final TooltipDefaultActionType? type; + final String? name; + final VoidCallback? onTap; + final double? borderWidth; + final Color? borderColor; + final bool shouldShowForFirstTooltip; + final bool shouldShowForLastTooltip; + + TooltipActionButton.withDefault({ + required this.type, + this.backgroundColor, + this.textStyle = const TextStyle( + color: Colors.white, + ), + this.borderRadius = const BorderRadius.all( + Radius.circular(50), + ), + this.padding = const EdgeInsets.symmetric( + horizontal: 15, + vertical: 4, + ), + this.leadIcon, + this.tailIcon, + this.name, + this.onTap, + this.borderColor, + this.borderWidth, + this.shouldShowForFirstTooltip = true, + this.shouldShowForLastTooltip = true, + }) : button = null; + + TooltipActionButton.custom({ + required this.button, + this.shouldShowForFirstTooltip = true, + this.shouldShowForLastTooltip = true, + }) : backgroundColor = null, + borderRadius = null, + textStyle = null, + padding = null, + leadIcon = null, + tailIcon = null, + type = null, + name = null, + onTap = null, + borderColor = null, + borderWidth = null; +} diff --git a/lib/src/models/tooltip_action_config.dart b/lib/src/models/tooltip_action_config.dart new file mode 100644 index 00000000..7c8fcd70 --- /dev/null +++ b/lib/src/models/tooltip_action_config.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +import '../../showcaseview.dart'; + +class TooltipActionConfig { + const TooltipActionConfig({ + this.alignment = TooltipActionAlignment.left, + this.actionGap = 5, + this.padding = EdgeInsets.zero, + this.position = TooltipActionPosition.inside, + this.gapBetweenContentAndAction = 10, + }); + + /// Defines tooltip action widget position. + /// It can be inside the tooltip widget or outside. + /// + /// Default to [TooltipActionPosition.inside] + final TooltipActionPosition position; + + /// Defines the alignment of actions buttons of tooltip action widget + /// + /// Default to [TooltipActionAlignment.left] + final TooltipActionAlignment alignment; + + /// Defines the gap between the actions buttons of tooltip action widget + /// + /// Default to 5.0 + final double actionGap; + + /// Defines the padding in the tooltip action widget + /// + /// Default to [EdgeInsets.zero] + final EdgeInsets padding; + + /// Defines vertically gap between tooltip content and actions. + /// + /// Default to 10.0 + final double gapBetweenContentAndAction; +} diff --git a/lib/src/showcase.dart b/lib/src/showcase.dart index 64d6b83b..2f3b4449 100644 --- a/lib/src/showcase.dart +++ b/lib/src/showcase.dart @@ -25,12 +25,11 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:showcaseview/showcaseview.dart'; -import 'enum.dart'; import 'get_position.dart'; import 'layout_overlays.dart'; import 'shape_clipper.dart'; -import 'showcase_widget.dart'; import 'tooltip_widget.dart'; class Showcase extends StatefulWidget { @@ -231,11 +230,6 @@ class Showcase extends StatefulWidget { /// Provides padding around the description. Default padding is zero. final EdgeInsets? descriptionPadding; - /// Provides tooTip action widgets at bottom in tool tip. - /// - /// one can use [DefaultToolTipActionWidget] class to use default action - final Widget? toolTipAction; - /// Provides text direction of tooltip title. final TextDirection? titleTextDirection; @@ -257,16 +251,16 @@ class Showcase extends StatefulWidget { /// Defaults to 7. final double toolTipSlideEndDistance; - /// Defines tooltip action widget position. - /// It can be inside the tooltip widget or outside. + /// Provides tooTip action widgets at bottom in tooltip. /// - /// Default to [TooltipActionPosition.inside] - final TooltipActionPosition tooltipActionPosition; + /// one can use [TooltipActionButton] class to use default action + final List? tooltipActions; - /// Defines gap between tooltip content and actions. + /// Provide a configuration for tooltip action widget like alignment, + /// position, gap, etc... /// - /// Default to 10 - final double gapBetweenContentAndAction; + /// Default to [const TooltipActionConfig()] + final TooltipActionConfig? tooltipActionConfig; const Showcase({ required this.key, @@ -309,16 +303,15 @@ class Showcase extends StatefulWidget { this.tooltipPosition, this.titlePadding, this.descriptionPadding, - this.toolTipAction, + this.tooltipActions, this.titleTextDirection, this.descriptionTextDirection, this.onBarrierClick, this.disableBarrierInteraction = false, this.toolTipSlideEndDistance = 7, - this.tooltipActionPosition = TooltipActionPosition.inside, - this.gapBetweenContentAndAction = 8, - }) : height = null, - width = null, + this.tooltipActionConfig, + }) : width = null, + height = null, container = null, assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0, "overlay opacity must be between 0 and 1."), @@ -358,8 +351,8 @@ class Showcase extends StatefulWidget { this.onBarrierClick, this.disableBarrierInteraction = false, this.toolTipSlideEndDistance = 7, - this.toolTipAction, - this.gapBetweenContentAndAction = 10, + this.tooltipActions, + this.tooltipActionConfig, }) : showArrow = false, onToolTipClick = null, scaleAnimationDuration = const Duration(milliseconds: 300), @@ -383,8 +376,7 @@ class Showcase extends StatefulWidget { assert(overlayOpacity >= 0.0 && overlayOpacity <= 1.0, "overlay opacity must be between 0 and 1."), assert(onBarrierClick == null || disableBarrierInteraction == false, - "can't use onBarrierClick & disableBarrierInteraction property at same time"), - tooltipActionPosition = TooltipActionPosition.inside; + "can't use onBarrierClick & disableBarrierInteraction property at same time"); @override State createState() => _ShowcaseState(); @@ -475,7 +467,6 @@ class _ShowcaseState extends State { screenWidth: size.width, screenHeight: size.height, ); - print(position?.getCenter()); return buildOverlayOnTarget(offset, rectBound.size, rectBound, size); }, showOverlay: true, @@ -652,17 +643,33 @@ class _ShowcaseState extends State { tooltipPosition: widget.tooltipPosition, titlePadding: widget.titlePadding, descriptionPadding: widget.descriptionPadding, - toolTipAction: widget.toolTipAction, titleTextDirection: widget.titleTextDirection, descriptionTextDirection: widget.descriptionTextDirection, toolTipSlideEndDistance: widget.toolTipSlideEndDistance, - tooltipActionPosition: widget.tooltipActionPosition, - gapBetweenContentAndAction: widget.gapBetweenContentAndAction, + showCaseState: ShowCaseWidget.of(context), + tooltipActionConfig: _getTooltipActionConfig(), + tooltipActions: _getTooltipActions(), ), ], ], ); } + + List _getTooltipActions() => + (widget.tooltipActions?.isEmpty ?? true) + ? ShowCaseWidget.of(context).globalTooltipActions ?? [] + : widget.tooltipActions ?? []; + + TooltipActionConfig _getTooltipActionConfig() { + final showCaseState = ShowCaseWidget.of(context); + if (widget.tooltipActionConfig != null) { + return widget.tooltipActionConfig!; + } else if (showCaseState.globalTooltipActionConfig != null) { + return showCaseState.globalTooltipActionConfig!; + } else { + return const TooltipActionConfig(); + } + } } class _TargetWidget extends StatelessWidget { diff --git a/lib/src/showcase_widget.dart b/lib/src/showcase_widget.dart index 16168ba6..00a91ec1 100644 --- a/lib/src/showcase_widget.dart +++ b/lib/src/showcase_widget.dart @@ -83,6 +83,9 @@ class ShowCaseWidget extends StatefulWidget { /// Enable/disable showcase globally. Enabled by default. final bool enableShowcase; + final List? globalTooltipActions; + final TooltipActionConfig? globalTooltipActionConfig; + const ShowCaseWidget({ required this.builder, this.onFinish, @@ -98,6 +101,8 @@ class ShowCaseWidget extends StatefulWidget { this.enableAutoScroll = false, this.disableBarrierInteraction = false, this.enableShowcase = true, + this.globalTooltipActionConfig, + this.globalTooltipActions, }); static GlobalKey? activeTargetWidget(BuildContext context) { @@ -126,6 +131,9 @@ class ShowCaseWidgetState extends State { Size? rootWidgetSize; Key? anchoredOverlayKey; + late final TooltipActionConfig? globalTooltipActionConfig; + late final List? globalTooltipActions; + /// These properties are only here so that it can be accessed by /// [Showcase] bool get autoPlay => widget.autoPlay; @@ -152,6 +160,8 @@ class ShowCaseWidgetState extends State { @override void initState() { super.initState(); + globalTooltipActions = widget.globalTooltipActions; + globalTooltipActionConfig = widget.globalTooltipActionConfig; initRootWidget(); } diff --git a/lib/src/tooltip_action_button.dart b/lib/src/tooltip_action_button.dart deleted file mode 100644 index 4991c733..00000000 --- a/lib/src/tooltip_action_button.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; - -class ToolTipActionButton extends StatelessWidget { - const ToolTipActionButton({ - Key? key, - required this.action, - required this.padding, - required this.widget, - required this.opacity, - }) : super(key: key); - - final VoidCallback? action; - final EdgeInsetsGeometry padding; - final Widget? widget; - final double opacity; - - @override - Widget build(BuildContext context) { - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: action, - child: IgnorePointer( - child: Opacity( - opacity: opacity, - child: Padding( - padding: padding, - child: widget, - ), - ), - ), - ); - } -} diff --git a/lib/src/tooltip_action_button_widget.dart b/lib/src/tooltip_action_button_widget.dart new file mode 100644 index 00000000..1a1e4504 --- /dev/null +++ b/lib/src/tooltip_action_button_widget.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +import '../showcaseview.dart'; + +class TooltipActionButtonWidget extends StatelessWidget { + const TooltipActionButtonWidget({ + super.key, + required this.config, + required this.showCaseState, + }); + + final TooltipActionButton config; + final ShowCaseWidgetState showCaseState; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return config.button ?? + GestureDetector( + onTap: handleOnTap, + child: Container( + padding: config.padding, + decoration: BoxDecoration( + color: config.backgroundColor ?? theme.primaryColor, + borderRadius: config.borderRadius, + border: Border.all( + color: config.borderColor ?? + config.backgroundColor ?? + theme.primaryColor, + width: config.borderWidth ?? 0, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (config.leadIcon != null) + Padding( + padding: config.leadIcon?.padding ?? + const EdgeInsets.only(right: 5), + child: config.leadIcon?.icon, + ), + Text( + config.name ?? config.type?.actionName ?? '', + style: config.textStyle, + ), + if (config.tailIcon != null) + Padding( + padding: config.tailIcon?.padding ?? + const EdgeInsets.only(left: 5), + child: config.tailIcon?.icon, + ), + ], + ), + ), + ); + } + + void handleOnTap() { + if (config.onTap != null) { + config.onTap?.call(); + } else { + switch (config.type) { + case TooltipDefaultActionType.next: + showCaseState.next(); + break; + case TooltipDefaultActionType.previous: + showCaseState.previous(); + break; + case TooltipDefaultActionType.skip: + showCaseState.dismiss(); + break; + default: + throw ArgumentError('Invalid tooltip default action type'); + } + } + } +} diff --git a/lib/src/tooltip_widget.dart b/lib/src/tooltip_widget.dart index 520d6fe7..c50a3abf 100644 --- a/lib/src/tooltip_widget.dart +++ b/lib/src/tooltip_widget.dart @@ -24,7 +24,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'enum.dart'; +import '../showcaseview.dart'; import 'get_position.dart'; import 'measure_size.dart'; import 'widget/tooltip_slide_transition.dart'; @@ -60,14 +60,14 @@ class ToolTipWidget extends StatefulWidget { final TooltipPosition? tooltipPosition; final EdgeInsets? titlePadding; final EdgeInsets? descriptionPadding; - final Widget? toolTipAction; + List? tooltipActions; final TextDirection? titleTextDirection; final TextDirection? descriptionTextDirection; final double toolTipSlideEndDistance; - final TooltipActionPosition tooltipActionPosition; - final double gapBetweenContentAndAction; + final ShowCaseWidgetState showCaseState; + TooltipActionConfig tooltipActionConfig; - const ToolTipWidget({ + ToolTipWidget({ super.key, required this.position, required this.offset, @@ -92,14 +92,14 @@ class ToolTipWidget extends StatefulWidget { required this.tooltipBorderRadius, required this.scaleAnimationDuration, required this.scaleAnimationCurve, - required this.tooltipActionPosition, - required this.gapBetweenContentAndAction, + required this.showCaseState, + required this.tooltipActionConfig, this.scaleAnimationAlignment, this.isTooltipDismissed = false, this.tooltipPosition, this.titlePadding, this.descriptionPadding, - this.toolTipAction, + this.tooltipActions, this.titleTextDirection, this.descriptionTextDirection, this.toolTipSlideEndDistance = 7, @@ -121,19 +121,25 @@ class _ToolTipWidgetState extends State late final Animation _scaleAnimation; double tooltipWidth = 0; - double toolTipHeight = 0; + double tooltipHeight = 0; double tooltipScreenEdgePadding = 20; double tooltipTextPadding = 15; double actionWidgetHeight = 0.0; + Size? tooltipActionSize; + final GlobalKey tooltipActionKey = GlobalKey(); + bool isOffstage = true; + + void setTooltipActionWidth(size) => tooltipActionSize ??= size; + TooltipPosition findPositionForContent(Offset position) { - var height = toolTipHeight; + var height = tooltipHeight; height = widget.contentHeight ?? height; final bottomPosition = position.dy + - ((widget.position?.getHeight() ?? 0) / 2) + + ((widget.position?.getHeight() ?? 0) * 0.5) + actionWidgetHeight; final topPosition = position.dy - - ((widget.position?.getHeight() ?? 0) / 2) - + ((widget.position?.getHeight() ?? 0) * 0.5) - actionWidgetHeight; final hasSpaceInTop = topPosition >= height; // TODO: need to update for flutter version > 3.8.X @@ -183,7 +189,7 @@ class _ToolTipWidgetState extends State if (maxTextWidth > widget.screenSize.width - tooltipScreenEdgePadding) { tooltipWidth = widget.screenSize.width - tooltipScreenEdgePadding; } else { - tooltipWidth = maxTextWidth + tooltipTextPadding; + tooltipWidth = maxTextWidth + 0; } } @@ -223,10 +229,10 @@ class _ToolTipWidgetState extends State } double _getSpace() { - var space = widget.position!.getCenter() - (widget.contentWidth! / 2); + var space = widget.position!.getCenter() - (widget.contentWidth! * 0.5); if (space + widget.contentWidth! > widget.screenSize.width) { space = widget.screenSize.width - widget.contentWidth! - 8; - } else if (space < (widget.contentWidth! / 2)) { + } else if (space < (widget.contentWidth! * 0.5)) { space = 16; } return space; @@ -260,6 +266,7 @@ class _ToolTipWidgetState extends State void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { + _getWidgetSize(); if (widget.container != null && _customContainerKey.currentContext != null && _customContainerKey.currentContext?.size != null) { @@ -337,6 +344,17 @@ class _ToolTipWidgetState extends State super.dispose(); } + void _getWidgetSize() { + if (tooltipActionSize == null) { + final renderBox = + tooltipActionKey.currentContext?.findRenderObject() as RenderBox?; + if (renderBox != null) { + tooltipActionSize = renderBox.size; + setState(() => isOffstage = false); + } + } + } + @override Widget build(BuildContext context) { // TODO: maybe all this calculation doesn't need to run here. Maybe all or some of it can be moved outside? @@ -368,181 +386,248 @@ class _ToolTipWidgetState extends State _scaleAnimationController.reverse(); } + final tooltipActionButtonAlignment = + widget.tooltipActionConfig.alignment.alignment; + final tooltipAdaptiveWidth = _getTooltipAdaptiveWidth(); + final tooltipActionsList = _getActionWidgets(); + + Widget tooltipActionWidget = Offstage( + offstage: true, + child: Material( + type: MaterialType.transparency, + child: Padding( + key: tooltipActionKey, + padding: widget.tooltipActionConfig.padding ?? EdgeInsets.zero, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: tooltipActionButtonAlignment, + children: tooltipActionsList, + ), + ), + ), + ); + if (widget.container == null) { - return Positioned( - top: contentY, - left: _getLeft(), - right: _getRight(), - child: ScaleTransition( - scale: _scaleAnimation, - alignment: widget.scaleAnimationAlignment ?? - Alignment( - _getAlignmentX(), - _getAlignmentY(), - ), - child: FractionalTranslation( - translation: Offset(0.0, contentFractionalOffset as double), - child: ToolTipSlideTransition( - position: Tween( - begin: Offset.zero, - end: Offset( - 0, - widget.toolTipSlideEndDistance * contentOffsetMultiplier, + if (isOffstage) { + return tooltipActionWidget; + } else { + return Positioned( + top: contentY, + left: _getLeft(), + right: _getRight(), + child: ScaleTransition( + scale: _scaleAnimation, + alignment: widget.scaleAnimationAlignment ?? + Alignment( + _getAlignmentX(), + _getAlignmentY(), ), - ).animate(_movingAnimation), - child: Material( - type: MaterialType.transparency, - child: MeasureSize( - onSizeChange: onTooltipSizeChanged, - child: Container( - padding: widget.showArrow - ? EdgeInsets.only( - top: paddingTop - (isArrowUp ? arrowHeight : 0), - bottom: - paddingBottom - (isArrowUp ? 0 : arrowHeight), - ) - : EdgeInsets.symmetric( - vertical: paddingTop, - ), - child: SizedBox( - width: tooltipWidth, - child: Column( - children: [ - if (widget.tooltipActionPosition == - TooltipActionPosition.outsideTop) ...[ - widget.toolTipAction ?? const SizedBox.shrink(), - SizedBox( - height: widget.gapBetweenContentAndAction, + child: FractionalTranslation( + translation: Offset(0.0, contentFractionalOffset as double), + child: ToolTipSlideTransition( + position: Tween( + begin: Offset.zero, + end: Offset( + 0, + widget.toolTipSlideEndDistance * contentOffsetMultiplier, + ), + ).animate(_movingAnimation), + child: Material( + type: MaterialType.transparency, + child: MeasureSize( + onSizeChange: onTooltipSizeChanged, + child: Container( + padding: widget.showArrow + ? EdgeInsets.only( + top: paddingTop - (isArrowUp ? arrowHeight : 0), + bottom: + paddingBottom - (isArrowUp ? 0 : arrowHeight), + ) + : EdgeInsets.symmetric( + vertical: paddingTop, ), - ], - 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: widget.showArrow && isArrowUp - ? arrowHeight - 1 - : 0, - bottom: widget.showArrow && !isArrowUp - ? arrowHeight - 1 - : 0, + child: SizedBox( + width: tooltipAdaptiveWidth, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (!isArrowUp && + widget.tooltipActionConfig.position == + TooltipActionPosition.outside && + tooltipActionsList.isNotEmpty) ...[ + Container( + width: tooltipAdaptiveWidth, + padding: widget.tooltipActionConfig.padding ?? + EdgeInsets.zero, + child: Row( + mainAxisAlignment: + tooltipActionButtonAlignment, + children: tooltipActionsList, ), - child: ClipRRect( - borderRadius: widget.tooltipBorderRadius ?? - BorderRadius.circular(8.0), - child: GestureDetector( - onTap: widget.onTooltipTap, - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: - MediaQuery.of(context).size.width - - 30, + ), + SizedBox( + height: widget.tooltipActionConfig + .gapBetweenContentAndAction, + ), + ], + 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: widget.showArrow && isArrowUp + ? arrowHeight - 1 + : 0, + bottom: widget.showArrow && !isArrowUp + ? arrowHeight - 1 + : 0, + ), + 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( - mainAxisSize: MainAxisSize.min, - 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 ?? + child: Center( + child: Column( + crossAxisAlignment: + widget.title != null + ? CrossAxisAlignment.start + : CrossAxisAlignment.center, + children: [ + if (widget.title != null) + SizedBox( + width: tooltipAdaptiveWidth, + child: 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( + .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, - ), - ), - ), - ), - if (widget.tooltipActionPosition == - TooltipActionPosition - .inside) ...[ SizedBox( - height: widget - .gapBetweenContentAndAction, + width: tooltipAdaptiveWidth, + child: 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, + ), + ), + ), + ), ), - widget.toolTipAction ?? - const SizedBox.shrink() + if (widget.tooltipActionConfig + .position == + TooltipActionPosition + .inside && + tooltipActionsList + .isNotEmpty) ...[ + SizedBox( + height: widget + .tooltipActionConfig + .gapBetweenContentAndAction, + ), + Padding( + padding: widget + .tooltipActionConfig + .padding ?? + EdgeInsets.zero, + child: Row( + mainAxisSize: + MainAxisSize.max, + mainAxisAlignment: + tooltipActionButtonAlignment, + children: + tooltipActionsList, + ), + ), + ] ], - ], + ), ), ), ), ), ), - ), - ], - ), - if (widget.tooltipActionPosition == - TooltipActionPosition.outsideBottom) ...[ - SizedBox( - height: widget.gapBetweenContentAndAction, + ], ), - widget.toolTipAction ?? const SizedBox.shrink(), + if (isArrowUp && + widget.tooltipActionConfig.position == + TooltipActionPosition.outside && + tooltipActionsList.isNotEmpty) ...[ + SizedBox( + height: widget.tooltipActionConfig + .gapBetweenContentAndAction, + ), + Container( + width: tooltipAdaptiveWidth, + padding: widget.tooltipActionConfig.padding ?? + EdgeInsets.zero, + child: Row( + mainAxisAlignment: + tooltipActionButtonAlignment, + children: tooltipActionsList, + ), + ), + ] ], - ], + ), ), ), ), @@ -550,53 +635,78 @@ class _ToolTipWidgetState extends State ), ), ), - ), - ); + ); + } } - return Stack( - children: [ - Positioned( - left: _getSpace(), - top: contentY - (10 * contentOffsetMultiplier), - child: ScaleTransition( - scale: _scaleAnimation, - alignment: widget.scaleAnimationAlignment ?? - Alignment( - _getAlignmentX(), - _getAlignmentY(), - ), - child: FractionalTranslation( - translation: Offset(0.0, contentFractionalOffset as double), - child: ToolTipSlideTransition( - position: Tween( - begin: Offset.zero, - end: Offset( - 0, - widget.toolTipSlideEndDistance * contentOffsetMultiplier, + if (isOffstage) { + return tooltipActionWidget; + } else { + return Stack( + children: [ + Positioned( + left: _getSpace(), + top: contentY - (10 * contentOffsetMultiplier), + child: ScaleTransition( + scale: _scaleAnimation, + alignment: widget.scaleAnimationAlignment ?? + Alignment( + _getAlignmentX(), + _getAlignmentY(), ), - ).animate(_movingAnimation), - child: Material( - color: Colors.transparent, - child: GestureDetector( - onTap: widget.onTooltipTap, - child: Container( - padding: EdgeInsets.only( - top: paddingTop, - bottom: paddingBottom, - ), - color: Colors.transparent, - child: Center( - child: MeasureSize( - onSizeChange: onSizeChange, - child: Column( - children: [ - widget.container!, - SizedBox( - width: widget.contentWidth, - child: widget.toolTipAction ?? - const SizedBox.shrink(), - ) - ], + child: FractionalTranslation( + translation: Offset(0.0, contentFractionalOffset as double), + child: ToolTipSlideTransition( + position: Tween( + begin: Offset.zero, + end: Offset( + 0, + widget.toolTipSlideEndDistance * contentOffsetMultiplier, + ), + ).animate(_movingAnimation), + child: Material( + color: Colors.transparent, + child: GestureDetector( + onTap: widget.onTooltipTap, + child: Container( + padding: EdgeInsets.only( + top: paddingTop, + bottom: paddingBottom, + ), + width: widget.contentWidth, + color: Colors.transparent, + child: Center( + child: MeasureSize( + onSizeChange: onSizeChange, + child: Column( + children: [ + widget.container!, + if (tooltipActionsList.isNotEmpty) + SizedBox( + height: widget.tooltipActionConfig + .gapBetweenContentAndAction, + ), + SizedBox( + width: max(widget.contentWidth ?? 0, + tooltipActionSize?.width ?? 0), + child: (tooltipActionsList.isNotEmpty) + ? SizedBox( + width: tooltipAdaptiveWidth, + child: Padding( + padding: widget.tooltipActionConfig + .padding ?? + EdgeInsets.zero, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + tooltipActionButtonAlignment, + children: tooltipActionsList, + ), + ), + ) + : const SizedBox.shrink(), + ) + ], + ), ), ), ), @@ -606,9 +716,48 @@ class _ToolTipWidgetState extends State ), ), ), + ], + ); + } + } + + double? _getTooltipAdaptiveWidth() => tooltipActionSize == null + ? null + : max( + tooltipWidth, + tooltipActionSize!.width + + (widget.tooltipActionConfig.position == + TooltipActionPosition.inside + ? (widget.tooltipPadding?.left ?? 0) + + (widget.tooltipPadding?.right ?? 0) + : 0), + ); + + List _getActionWidgets() { + List actions = []; + for (var i = 0; i < (widget.tooltipActions?.length ?? 0); i++) { + if ((widget.showCaseState.activeWidgetId == 0 && + !widget.tooltipActions![i].shouldShowForFirstTooltip) || + (widget.showCaseState.activeWidgetId == + (widget.showCaseState.ids?.length ?? 0) - 1 && + !widget.tooltipActions![i].shouldShowForLastTooltip)) { + continue; + } + actions.add( + Padding( + padding: EdgeInsets.only( + right: i < widget.tooltipActions!.length - 1 + ? widget.tooltipActionConfig.actionGap + : 0, + ), + child: TooltipActionButtonWidget( + config: widget.tooltipActions![i], + showCaseState: widget.showCaseState, + ), ), - ], - ); + ); + } + return actions; } void onSizeChange(Size? size) { @@ -650,7 +799,7 @@ class _ToolTipWidgetState extends State } else { tooltipWidth = size.width; } - toolTipHeight = size.height; + tooltipHeight = size.height; }); } }