diff --git a/CHANGELOG.md b/CHANGELOG.md index 8414db35..74fbd516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ ## [4.0.0] (unreleased) - [BREAKING] Fixed [#457](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/457) titleAlignment property does not work -- Feature ✨: Added Action widget for tooltip +- Feature [#466](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/pull/466): Added Action widget for tooltip - Feature [#475](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/475) - Add feasibility to change margin of tooltip with `toolTipMargin`. - Feature [#478](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/478) - Added feasibility to change auto scroll widget alignment `scrollAlignment`. -- Feature ✨: Added `enableAutoScroll` to `showcase`. +- Feature [#386](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/pull/386): Added `enableAutoScroll` to `showcase`. +- Feature [#395](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/395) - + Added `floatingActionWidget` to give a static fixed widget at any place on the screen. +- Feature [#396](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/pull/396): Added `globalFloatingActionWidget` and `hideFloatingActionWidgetForShowcase` for global + static fixed widget ## [3.0.0] - [BREAKING] Fixed [#434](https://github.com/SimformSolutionsPvtLtd/flutter_showcaseview/issues/434) removed deprecated text style after Flutter 3.22 follow [migration guide](https://docs.flutter.dev/release/breaking-changes/3-19-deprecations#texttheme) diff --git a/README.md b/README.md index 4cf29b9c..9c0553f5 100644 --- a/README.md +++ b/README.md @@ -136,26 +136,29 @@ WidgetsBinding.instance.addPostFrameCallback((_) => ## Properties of `ShowCaseWidget`: -| Name | Type | Default Behaviour | Description | -|---------------------------|----------------------------|------------------------------|--------------------------------------------------------------------------------| -| builder | Builder | | | -| blurValue | double | 0 | Provides blur effect on overlay | -| autoPlay | bool | false | Automatically display Next showcase | -| autoPlayDelay | Duration | Duration(milliseconds: 2000) | Visibility time of showcase when `autoplay` is enabled | -| enableAutoPlayLock | bool | false | Block the user interaction on overlay when autoPlay is enabled. | -| enableAutoScroll | bool | false | Allows to auto scroll to next showcase so as to make the given target visible. | -| scrollDuration | Duration | Duration(milliseconds: 300) | Time duration for auto scrolling | -| disableBarrierInteraction | bool | false | Disable barrier interaction | -| disableScaleAnimation | bool | false | Disable scale transition for all showcases | -| disableMovingAnimation | bool | false | Disable bouncing/moving transition for all showcases | -| onStart | Function(int?, GlobalKey)? | | Triggered on start of each showcase. | -| 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. | -| toolTipMargin | double | 14 | For tooltip margin | -| globalTooltipActionConfig | TooltipActionConfig? | | Global tooltip actionbar config | -| globalTooltipActions | List? | | Global list of tooltip actions | -| scrollAlignment | double | 0.5 | For Auto scroll widget alignment | +| Name | Type | Default Behaviour | Description | +|-------------------------------------|----------------------------------------------|------------------------------|--------------------------------------------------------------------------------| +| builder | Builder | | | +| blurValue | double | 0 | Provides blur effect on overlay. | +| autoPlay | bool | false | Automatically display Next showcase. | +| autoPlayDelay | Duration | Duration(milliseconds: 2000) | Visibility time of showcase when `autoplay` is enabled. | +| enableAutoPlayLock | bool | false | Block the user interaction on overlay when autoPlay is enabled. | +| enableAutoScroll | bool | false | Allows to auto scroll to next showcase so as to make the given target visible. | +| scrollDuration | Duration | Duration(milliseconds: 300) | Time duration for auto scrolling. | +| disableBarrierInteraction | bool | false | Disable barrier interaction. | +| disableScaleAnimation | bool | false | Disable scale transition for all showcases. | +| disableMovingAnimation | bool | false | Disable bouncing/moving transition for all showcases. | +| onStart | Function(int?, GlobalKey)? | | Triggered on start of each showcase. | +| 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. | +| toolTipMargin | double | 14 | For tooltip margin. | +| globalTooltipActionConfig | TooltipActionConfig? | | Global tooltip actionbar config. | +| globalTooltipActions | List? | | Global list of tooltip actions . | +| scrollAlignment | double | 0.5 | For Auto scroll widget alignment. | +| globalFloatingActionWidget | FloatingActionWidget Function(BuildContext)? | | Global Config for tooltip action to auto apply for all the toolTip . | +| hideFloatingActionWidgetForShowcase | List | [] | Hides globalFloatingActionWidget for the provided showcase widget keys. | + ## Properties of `Showcase` and `Showcase.withWidget`: @@ -210,6 +213,7 @@ WidgetsBinding.instance.addPostFrameCallback((_) => | tooltipActions | List? | [] | Provide a list of tooltip actions | ✅ | ✅ | | tooltipActionConfig | TooltipActionConfig? | | Give configurations (alignment, position, etc...) to the tooltip actionbar | ✅ | ✅ | | enableAutoScroll | bool? | ShowCaseWidget.enableAutoScroll | This is used to override the `ShowCaseWidget.enableAutoScroll` behaviour | ✅ | ✅ | +| floatingActionWidget | FloatingActionWidget | | Provided a floating static action widget to show at any place on the screen | ✅ | ✅ | ## Properties of `TooltipActionButton` and `TooltipActionButton.custom`: diff --git a/example/lib/main.dart b/example/lib/main.dart index 2e8fd988..efcee2c2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -26,6 +26,27 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, home: Scaffold( body: ShowCaseWidget( + hideFloatingActionWidgetForShowcase: [_lastShowcaseWidget], + globalFloatingActionWidget: (showcaseContext) => FloatingActionWidget( + left: 16, + bottom: 16, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: ShowCaseWidget.of(showcaseContext).dismiss, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffEE5366), + ), + child: const Text( + 'Skip', + style: TextStyle( + color: Colors.white, + fontSize: 15, + ), + ), + ), + ), + ), onStart: (index, key) { log('onStart: $index, $key'); }, @@ -207,8 +228,18 @@ class _MailPageState extends State { Showcase( key: _firstShowcaseWidget, description: 'Tap to see menu options', - onBarrierClick: () => - debugPrint('Barrier clicked'), + onBarrierClick: () { + debugPrint('Barrier clicked'); + debugPrint( + 'Floating Action widget for first ' + 'showcase is now hidden', + ); + ShowCaseWidget.of(context) + .hideFloatingActionWidgetForKeys([ + _firstShowcaseWidget, + _lastShowcaseWidget + ]); + }, tooltipActionConfig: const TooltipActionConfig( alignment: MainAxisAlignment.end, @@ -256,6 +287,26 @@ class _MailPageState extends State { "Tap to see profile which contains user's name, profile picture, mobile number and country", tooltipBackgroundColor: Theme.of(context).primaryColor, textColor: Colors.white, + floatingActionWidget: FloatingActionWidget( + left: 16, + bottom: 16, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xffEE5366), + ), + onPressed: ShowCaseWidget.of(context).dismiss, + child: const Text( + 'Close Showcase', + style: TextStyle( + color: Colors.white, + fontSize: 15, + ), + ), + ), + ), + ), targetShapeBorder: const CircleBorder(), tooltipActionConfig: const TooltipActionConfig( alignment: MainAxisAlignment.spaceBetween, diff --git a/lib/showcaseview.dart b/lib/showcaseview.dart index 3da2f1cd..3d597dd7 100644 --- a/lib/showcaseview.dart +++ b/lib/showcaseview.dart @@ -29,3 +29,4 @@ export 'src/models/tooltip_action_config.dart'; export 'src/showcase.dart'; export 'src/showcase_widget.dart'; export 'src/tooltip_action_button_widget.dart'; +export 'src/widget/floating_action_widget.dart'; diff --git a/lib/src/showcase.dart b/lib/src/showcase.dart index ae9fad84..e38a8ef8 100644 --- a/lib/src/showcase.dart +++ b/lib/src/showcase.dart @@ -35,6 +35,7 @@ import 'shape_clipper.dart'; import 'showcase_widget.dart'; import 'tooltip_action_button_widget.dart'; import 'tooltip_widget.dart'; +import 'widget/floating_action_widget.dart'; class Showcase extends StatefulWidget { /// A key that is unique across the entire app. @@ -96,6 +97,10 @@ class Showcase extends StatefulWidget { /// Custom tooltip widget when [Showcase.withWidget] is used. final Widget? container; + /// Custom static floating action widget to show a static widget anywhere + /// on the screen + final FloatingActionWidget? floatingActionWidget; + /// Defines background color for tooltip widget. /// /// Default to [Colors.white] @@ -415,6 +420,7 @@ class Showcase extends StatefulWidget { this.tooltipActionConfig, this.scrollAlignment = 0.5, this.enableAutoScroll, + this.floatingActionWidget, }) : height = null, width = null, container = null, @@ -467,6 +473,7 @@ class Showcase extends StatefulWidget { /// - `toolTipSlideEndDistance`: The distance the tooltip slides in from the edge of the screen (defaults to 7dp). /// - `tooltipActions`: A list of custom actions (widgets) to display within the tooltip. /// - `tooltipActionConfig`: Configuration options for custom tooltip actions. + /// - `floatingActionWidget`: Custom static floating action widget to show a static widget anywhere /// /// **Differences from default constructor:** /// @@ -484,6 +491,7 @@ class Showcase extends StatefulWidget { required this.width, required this.container, required this.child, + this.floatingActionWidget, this.targetShapeBorder = const RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(8), @@ -554,6 +562,7 @@ class _ShowcaseState extends State { RenderBox? rootRenderObject; late final showCaseWidgetState = ShowCaseWidget.of(context); + FloatingActionWidget? _globalFloatingActionWidget; @override void initState() { @@ -569,6 +578,8 @@ class _ShowcaseState extends State { recalculateRootWidgetSize(); if (_enableShowcase) { + _globalFloatingActionWidget = + showCaseWidgetState.globalFloatingActionWidget?.call(context); final size = MediaQuery.of(context).size; position ??= GetPosition( rootRenderObject: rootRenderObject, @@ -786,6 +797,8 @@ class _ShowcaseState extends State { titleTextStyle: widget.titleTextStyle, descTextStyle: widget.descTextStyle, container: widget.container, + floatingActionWidget: + widget.floatingActionWidget ?? _globalFloatingActionWidget, tooltipBackgroundColor: widget.tooltipBackgroundColor, textColor: widget.textColor, showArrow: widget.showArrow, diff --git a/lib/src/showcase_widget.dart b/lib/src/showcase_widget.dart index cef0730a..9e3f60e0 100644 --- a/lib/src/showcase_widget.dart +++ b/lib/src/showcase_widget.dart @@ -20,10 +20,14 @@ * SOFTWARE. */ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import '../showcaseview.dart'; +typedef FloatingActionBuilderCallback = FloatingActionWidget Function( + BuildContext); + class ShowCaseWidget extends StatefulWidget { final WidgetBuilder builder; @@ -83,12 +87,23 @@ class ShowCaseWidget extends StatefulWidget { /// Enable/disable showcase globally. Enabled by default. final bool enableShowcase; + /// Custom static floating action widget to show a static widget anywhere + /// on the screen for all the showcase widget + /// Use this context to access showcaseWidget operation otherwise it will + /// throw error. + final FloatingActionBuilderCallback? globalFloatingActionWidget; + /// Global action to apply on every tooltip widget final List? globalTooltipActions; - /// Global Config for tooltip action to auto apply for all the toolTip + /// Global Config for tooltip action to auto apply for all the toolTip. final TooltipActionConfig? globalTooltipActionConfig; + /// Hides [globalFloatingActionWidget] for the provided showcase widgets. Add key of + /// showcase in which [globalFloatingActionWidget] should be hidden this list. + /// Defaults to []. + final List hideFloatingActionWidgetForShowcase; + /// A widget that manages multiple Showcase widgets. /// /// This widget provides a way to sequentially showcase multiple widgets @@ -115,6 +130,8 @@ class ShowCaseWidget extends StatefulWidget { /// - `enableShowcase`: Enables or disables the showcase functionality globally (defaults to `true`). /// - `globalTooltipActions`: A list of custom actions to be added to all tooltips. /// - `globalTooltipActionConfig`: Configuration options for the global tooltip actions. + /// - `globalFloatingActionWidget`: Custom static floating action widget to show a static widget anywhere for all the showcase widgets. + /// - `hideFloatingActionWidgetForShowcase`: Hides a [globalFloatingActionWidget] for the provided showcase keys. const ShowCaseWidget({ required this.builder, this.onFinish, @@ -132,6 +149,8 @@ class ShowCaseWidget extends StatefulWidget { this.enableShowcase = true, this.globalTooltipActionConfig, this.globalTooltipActions, + this.globalFloatingActionWidget, + this.hideFloatingActionWidgetForShowcase = const [], }); static GlobalKey? activeTargetWidget(BuildContext context) { @@ -184,9 +203,36 @@ class ShowCaseWidgetState extends State { bool get isShowCaseCompleted => ids == null && activeWidgetId == null; + List get hiddenFloatingActionKeys => + _hideFloatingWidgetKeys.keys.toList(); + + /// This Stores keys of showcase for which we will hide the + /// [globalFloatingActionWidget]. + late final _hideFloatingWidgetKeys = { + for (final item in widget.hideFloatingActionWidgetForShowcase) item: true + }; + /// Returns value of [ShowCaseWidget.blurValue] double get blurValue => widget.blurValue; + /// Returns current active showcase key + GlobalKey? get getCurrentActiveShowcaseKey { + if (ids == null || activeWidgetId == null) return null; + + if (activeWidgetId! < ids!.length && activeWidgetId! >= 0) { + return ids![activeWidgetId!]; + } else { + return null; + } + } + + /// Return a [widget.globalFloatingActionWidget] if not need to hide this for + /// current showcase. + FloatingActionBuilderCallback? get globalFloatingActionWidget => + _hideFloatingWidgetKeys[getCurrentActiveShowcaseKey] ?? false + ? null + : widget.globalFloatingActionWidget; + @override void initState() { super.initState(); @@ -294,6 +340,15 @@ class ShowCaseWidgetState extends State { activeWidgetId = null; } + /// Disables the [globalFloatingActionWidget] for the provided keys. + void hideFloatingActionWidgetForKeys( + List updatedList, + ) { + _hideFloatingWidgetKeys + ..clear() + ..addAll({for (final item in updatedList) item: true}); + } + @override Widget build(BuildContext context) { return _InheritedShowCaseView( diff --git a/lib/src/tooltip_widget.dart b/lib/src/tooltip_widget.dart index 047a331f..b6812dae 100644 --- a/lib/src/tooltip_widget.dart +++ b/lib/src/tooltip_widget.dart @@ -29,6 +29,7 @@ import 'get_position.dart'; import 'measure_size.dart'; import 'models/tooltip_action_config.dart'; import 'widget/action_widget.dart'; +import 'widget/floating_action_widget.dart'; import 'widget/tooltip_slide_transition.dart'; class ToolTipWidget extends StatefulWidget { @@ -44,6 +45,7 @@ class ToolTipWidget extends StatefulWidget { final TextStyle? titleTextStyle; final TextStyle? descTextStyle; final Widget? container; + final FloatingActionWidget? floatingActionWidget; final Color? tooltipBackgroundColor; final Color? textColor; final bool showArrow; @@ -79,6 +81,7 @@ class ToolTipWidget extends StatefulWidget { required this.titleTextStyle, required this.descTextStyle, required this.container, + required this.floatingActionWidget, required this.tooltipBackgroundColor, required this.textColor, required this.showArrow, @@ -470,7 +473,7 @@ class _ToolTipWidgetState extends State } if (widget.container == null) { - return Positioned( + final defaultToolTipWidget = Positioned( top: contentY, left: _getLeft(), right: _getRight(), @@ -651,8 +654,22 @@ class _ToolTipWidgetState extends State ), ), ); + + if (widget.floatingActionWidget == null) { + return defaultToolTipWidget; + } else { + return Stack( + fit: StackFit.expand, + children: [ + defaultToolTipWidget, + widget.floatingActionWidget!, + ], + ); + } } + return Stack( + fit: StackFit.expand, children: [ Positioned( left: _getSpace(), @@ -720,6 +737,7 @@ class _ToolTipWidgetState extends State ), ), ), + if (widget.floatingActionWidget != null) widget.floatingActionWidget!, ], ); } diff --git a/lib/src/widget/floating_action_widget.dart b/lib/src/widget/floating_action_widget.dart new file mode 100644 index 00000000..3edb451d --- /dev/null +++ b/lib/src/widget/floating_action_widget.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; + +class FloatingActionWidget extends StatelessWidget { + const FloatingActionWidget({ + super.key, + required this.child, + this.right, + this.width, + this.height, + this.left, + this.bottom, + this.top, + }); + + /// This is same as the Positioned.directional widget + /// Creates a widget that controls where a child of a [Stack] is positioned. + /// + /// Only two out of the three horizontal values (`start`, `end`, + /// [width]), and only two out of the three vertical values ([top], + /// [bottom], [height]), can be set. In each case, at least one of + /// the three must be null. + /// + /// If `textDirection` is [TextDirection.rtl], then the `start` argument is + /// used for the [right] property and the `end` argument is used for the + /// [left] property. Otherwise, if `textDirection` is [TextDirection.ltr], + /// then the `start` argument is used for the [left] property and the `end` + /// argument is used for the [right] property. + factory FloatingActionWidget.directional({ + Key? key, + required TextDirection textDirection, + required Widget child, + double? start, + double? top, + double? end, + double? bottom, + double? width, + double? height, + }) { + /// Default value will be [TextDirection.ltr]. + var left = start; + var right = end; + switch (textDirection) { + case TextDirection.ltr: + break; + case TextDirection.rtl: + left = end; + right = start; + break; + } + return FloatingActionWidget( + key: key, + left: left, + top: top, + right: right, + bottom: bottom, + width: width, + height: height, + child: child, + ); + } + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.ProxyWidget.child} + final Widget child; + + /// The distance that the child's left edge is inset from the left of the stack. + /// + /// Only two out of the three horizontal values ([left], [right], [width]) can be + /// set. The third must be null. + /// + /// If all three are null, the [Stack.alignment] is used to position the child + /// horizontally. + final double? left; + + /// The distance that the child's top edge is inset from the top of the stack. + /// + /// Only two out of the three vertical values ([top], [bottom], [height]) can be + /// set. The third must be null. + /// + /// If all three are null, the [Stack.alignment] is used to position the child + /// vertically. + final double? top; + + /// The distance that the child's right edge is inset from the right of the stack. + /// + /// Only two out of the three horizontal values ([left], [right], [width]) can be + /// set. The third must be null. + /// + /// If all three are null, the [Stack.alignment] is used to position the child + /// horizontally. + final double? right; + + /// The distance that the child's bottom edge is inset from the bottom of the stack. + /// + /// Only two out of the three vertical values ([top], [bottom], [height]) can be + /// set. The third must be null. + /// + /// If all three are null, the [Stack.alignment] is used to position the child + /// vertically. + final double? bottom; + + /// The child's width. + /// + /// Only two out of the three horizontal values ([left], [right], [width]) can be + /// set. The third must be null. + /// + /// If all three are null, the [Stack.alignment] is used to position the child + /// horizontally. + final double? width; + + /// The child's height. + /// + /// Only two out of the three vertical values ([top], [bottom], [height]) can be + /// set. The third must be null. + /// + /// If all three are null, the [Stack.alignment] is used to position the child + /// vertically. + final double? height; + + @override + Widget build(BuildContext context) { + return Positioned( + key: key, + left: left, + top: top, + right: right, + bottom: bottom, + width: width, + height: height, + child: child, + ); + } +}