From 70b21c20ca16d0065663e9c32de9139613a425bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkay=20O=CC=88z?= Date: Wed, 27 Apr 2022 13:58:47 +0200 Subject: [PATCH 1/4] Collect tap position on clicking targets --- lib/src/widgets/animated_focus_light.dart | 90 +++++++++---------- .../widgets/tutorial_coach_mark_widget.dart | 19 ++-- lib/tutorial_coach_mark.dart | 3 + 3 files changed, 55 insertions(+), 57 deletions(-) diff --git a/lib/src/widgets/animated_focus_light.dart b/lib/src/widgets/animated_focus_light.dart index 7922cfc..a6edf71 100644 --- a/lib/src/widgets/animated_focus_light.dart +++ b/lib/src/widgets/animated_focus_light.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:tutorial_coach_mark/src/paint/light_paint.dart'; import 'package:tutorial_coach_mark/src/paint/light_paint_rect.dart'; import 'package:tutorial_coach_mark/src/target/target_focus.dart'; @@ -12,6 +11,7 @@ class AnimatedFocusLight extends StatefulWidget { final List targets; final Function(TargetFocus)? focus; final FutureOr Function(TargetFocus)? clickTarget; + final FutureOr Function(TargetFocus, TapDownDetails)? clickTargetWithTapPosition; final FutureOr Function(TargetFocus)? clickOverlay; final Function? removeFocus; final Function()? finish; @@ -30,6 +30,7 @@ class AnimatedFocusLight extends StatefulWidget { this.finish, this.removeFocus, this.clickTarget, + this.clickTargetWithTapPosition, this.clickOverlay, this.paddingFocus = 10, this.colorShadow = Colors.black, @@ -42,13 +43,11 @@ class AnimatedFocusLight extends StatefulWidget { super(key: key); @override - AnimatedFocusLightState createState() => pulseEnable - ? AnimatedPulseFocusLightState() - : AnimatedStaticFocusLightState(); + AnimatedFocusLightState createState() => + pulseEnable ? AnimatedPulseFocusLightState() : AnimatedStaticFocusLightState(); } -abstract class AnimatedFocusLightState extends State - with TickerProviderStateMixin { +abstract class AnimatedFocusLightState extends State with TickerProviderStateMixin { final borderRadiusDefault = 10.0; final defaultFocusAnimationDuration = Duration(milliseconds: 600); late AnimationController _controller; @@ -69,9 +68,7 @@ abstract class AnimatedFocusLightState extends State _targetFocus = widget.targets[_currentFocus]; _controller = AnimationController( vsync: this, - duration: _targetFocus.focusAnimationDuration ?? - widget.focusAnimationDuration ?? - defaultFocusAnimationDuration, + duration: _targetFocus.focusAnimationDuration ?? widget.focusAnimationDuration ?? defaultFocusAnimationDuration, )..addStatusListener(_listener); _curvedAnimation = CurvedAnimation( @@ -105,6 +102,10 @@ abstract class AnimatedFocusLightState extends State } } + Future _tapHandlerForPosition(TapDownDetails tapDetails) async { + await widget.clickTargetWithTapPosition?.call(_targetFocus, tapDetails); + } + void _runFocus(); void _nextFocus() { @@ -172,9 +173,7 @@ class AnimatedStaticFocusLightState extends AnimatedFocusLightState { @override Widget build(BuildContext context) { return InkWell( - onTap: _targetFocus.enableOverlayTab - ? () => _tapHandler(overlayTap: true) - : null, + onTap: _targetFocus.enableOverlayTab ? () => _tapHandler(overlayTap: true) : null, child: AnimatedBuilder( animation: _controller, builder: (_, child) { @@ -189,20 +188,18 @@ class AnimatedStaticFocusLightState extends AnimatedFocusLightState { ), ), Positioned( - left: - (_targetPosition?.offset.dx ?? 0) - _getPaddingFocus() * 2, + left: (_targetPosition?.offset.dx ?? 0) - _getPaddingFocus() * 2, top: (_targetPosition?.offset.dy ?? 0) - _getPaddingFocus() * 2, child: InkWell( borderRadius: _betBorderRadiusTarget(), - onTap: _targetFocus.enableTargetTab - ? () => _tapHandler(targetTap: true) - : null, + onTapDown: (details) { + _tapHandlerForPosition(details); + }, + onTap: _targetFocus.enableTargetTab ? () => _tapHandler(targetTap: true) : null, child: Container( color: Colors.transparent, - width: (_targetPosition?.size.width ?? 0) + - _getPaddingFocus() * 4, - height: (_targetPosition?.size.height ?? 0) + - _getPaddingFocus() * 4, + width: (_targetPosition?.size.width ?? 0) + _getPaddingFocus() * 4, + height: (_targetPosition?.size.height ?? 0) + _getPaddingFocus() * 4, ), ), ) @@ -228,14 +225,18 @@ class AnimatedStaticFocusLightState extends AnimatedFocusLightState { _controller.reverse(); } + @override + Future _tapHandlerForPosition(TapDownDetails tapDetails) async { + await super._tapHandlerForPosition(tapDetails); + } + @override void _runFocus() { if (_currentFocus < 0) return; _targetFocus = widget.targets[_currentFocus]; - _controller.duration = _targetFocus.focusAnimationDuration ?? - widget.focusAnimationDuration ?? - defaultFocusAnimationDuration; + _controller.duration = + _targetFocus.focusAnimationDuration ?? widget.focusAnimationDuration ?? defaultFocusAnimationDuration; var targetPosition = getTargetCurrent(_targetFocus); @@ -298,9 +299,7 @@ class AnimatedPulseFocusLightState extends AnimatedFocusLightState { duration: widget.pulseAnimationDuration ?? defaultPulseAnimationDuration, ); - _tweenPulse = _createTweenAnimation(_targetFocus.pulseVariation ?? - widget.pulseVariation ?? - defaultPulseVariation); + _tweenPulse = _createTweenAnimation(_targetFocus.pulseVariation ?? widget.pulseVariation ?? defaultPulseVariation); _controllerPulse.addStatusListener(_listenerPulse); } @@ -308,9 +307,7 @@ class AnimatedPulseFocusLightState extends AnimatedFocusLightState { @override Widget build(BuildContext context) { return InkWell( - onTap: _targetFocus.enableOverlayTab - ? () => _tapHandler(overlayTap: true) - : null, + onTap: _targetFocus.enableOverlayTab ? () => _tapHandler(overlayTap: true) : null, child: AnimatedBuilder( animation: _controller, builder: (_, child) { @@ -331,21 +328,18 @@ class AnimatedPulseFocusLightState extends AnimatedFocusLightState { ), ), Positioned( - left: (_targetPosition?.offset.dx ?? 0) - - _getPaddingFocus() * 2, - top: (_targetPosition?.offset.dy ?? 0) - - _getPaddingFocus() * 2, + left: (_targetPosition?.offset.dx ?? 0) - _getPaddingFocus() * 2, + top: (_targetPosition?.offset.dy ?? 0) - _getPaddingFocus() * 2, child: InkWell( borderRadius: _betBorderRadiusTarget(), - onTap: _targetFocus.enableTargetTab - ? () => _tapHandler(targetTap: true) - : null, + onTap: _targetFocus.enableTargetTab ? () => _tapHandler(targetTap: true) : null, + onTapDown: (details) { + _tapHandlerForPosition(details); + }, child: Container( color: Colors.transparent, - width: (_targetPosition?.size.width ?? 0) + - _getPaddingFocus() * 4, - height: (_targetPosition?.size.height ?? 0) + - _getPaddingFocus() * 4, + width: (_targetPosition?.size.width ?? 0) + _getPaddingFocus() * 4, + height: (_targetPosition?.size.height ?? 0) + _getPaddingFocus() * 4, ), ), ) @@ -363,13 +357,10 @@ class AnimatedPulseFocusLightState extends AnimatedFocusLightState { if (_currentFocus < 0) return; _targetFocus = widget.targets[_currentFocus]; - _controller.duration = _targetFocus.focusAnimationDuration ?? - widget.focusAnimationDuration ?? - defaultFocusAnimationDuration; + _controller.duration = + _targetFocus.focusAnimationDuration ?? widget.focusAnimationDuration ?? defaultFocusAnimationDuration; - _tweenPulse = _createTweenAnimation(_targetFocus.pulseVariation ?? - widget.pulseVariation ?? - defaultPulseVariation); + _tweenPulse = _createTweenAnimation(_targetFocus.pulseVariation ?? widget.pulseVariation ?? defaultPulseVariation); var targetPosition = getTargetCurrent(_targetFocus); @@ -415,6 +406,11 @@ class AnimatedPulseFocusLightState extends AnimatedFocusLightState { _controllerPulse.reverse(from: _controllerPulse.value); } + @override + Future _tapHandlerForPosition(TapDownDetails tapDetails) async { + await super._tapHandlerForPosition(tapDetails); + } + @override void dispose() { _controllerPulse.dispose(); diff --git a/lib/src/widgets/tutorial_coach_mark_widget.dart b/lib/src/widgets/tutorial_coach_mark_widget.dart index 0fbd1df..0ce84f5 100644 --- a/lib/src/widgets/tutorial_coach_mark_widget.dart +++ b/lib/src/widgets/tutorial_coach_mark_widget.dart @@ -13,6 +13,7 @@ class TutorialCoachMarkWidget extends StatefulWidget { this.finish, this.paddingFocus = 10, this.clickTarget, + this.onClickTargetWithTapPosition, this.clickOverlay, this.alignSkip = Alignment.bottomRight, this.textSkip = "SKIP", @@ -31,6 +32,7 @@ class TutorialCoachMarkWidget extends StatefulWidget { final List targets; final FutureOr Function(TargetFocus)? clickTarget; + final FutureOr Function(TargetFocus, TapDownDetails)? onClickTargetWithTapPosition; final FutureOr Function(TargetFocus)? clickOverlay; final Function()? finish; final Color colorShadow; @@ -51,8 +53,7 @@ class TutorialCoachMarkWidget extends StatefulWidget { TutorialCoachMarkWidgetState createState() => TutorialCoachMarkWidgetState(); } -class TutorialCoachMarkWidgetState extends State - implements TutorialCoachMarkController { +class TutorialCoachMarkWidgetState extends State implements TutorialCoachMarkController { final GlobalKey _focusLightKey = GlobalKey(); bool showContent = false; TargetFocus? currentTarget; @@ -77,6 +78,9 @@ class TutorialCoachMarkWidgetState extends State clickTarget: (target) { return widget.clickTarget?.call(target); }, + clickTargetWithTapPosition: (target, tapDetails) { + return widget.onClickTargetWithTapPosition?.call(target, tapDetails); + }, clickOverlay: (target) { return widget.clickOverlay?.call(target); }, @@ -124,9 +128,7 @@ class TutorialCoachMarkWidgetState extends State double haloHeight; if (currentTarget!.shape == ShapeLightFocus.Circle) { - haloWidth = target.size.width > target.size.height - ? target.size.width - : target.size.height; + haloWidth = target.size.width > target.size.height ? target.size.width : target.size.height; haloHeight = haloWidth; } else { haloWidth = target.size.width; @@ -157,8 +159,7 @@ class TutorialCoachMarkWidgetState extends State weight = MediaQuery.of(context).size.width; left = 0; top = null; - bottom = haloHeight + - (MediaQuery.of(context).size.height - positioned.dy); + bottom = haloHeight + (MediaQuery.of(context).size.height - positioned.dy); } break; case ContentAlign.left: @@ -197,9 +198,7 @@ class TutorialCoachMarkWidgetState extends State width: weight, child: Padding( padding: i.padding, - child: i.builder != null - ? i.builder?.call(context, this) - : (i.child ?? SizedBox.shrink()), + child: i.builder != null ? i.builder?.call(context, this) : (i.child ?? SizedBox.shrink()), ), ), ); diff --git a/lib/tutorial_coach_mark.dart b/lib/tutorial_coach_mark.dart index 72cba5a..76356d0 100644 --- a/lib/tutorial_coach_mark.dart +++ b/lib/tutorial_coach_mark.dart @@ -15,6 +15,7 @@ class TutorialCoachMark { final BuildContext _context; final List targets; final FutureOr Function(TargetFocus)? onClickTarget; + final FutureOr Function(TargetFocus, TapDownDetails)? onClickTargetWithTapPosition; final FutureOr Function(TargetFocus)? onClickOverlay; final Function()? onFinish; final double paddingFocus; @@ -37,6 +38,7 @@ class TutorialCoachMark { {required this.targets, this.colorShadow = Colors.black, this.onClickTarget, + this.onClickTargetWithTapPosition, this.onClickOverlay, this.onFinish, this.paddingFocus = 10, @@ -59,6 +61,7 @@ class TutorialCoachMark { key: _widgetKey, targets: targets, clickTarget: onClickTarget, + onClickTargetWithTapPosition: onClickTargetWithTapPosition, clickOverlay: onClickOverlay, paddingFocus: paddingFocus, onClickSkip: skip, From 71e8598572002da48e1055aa2eb5cc927873b7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkay=20O=CC=88z?= Date: Wed, 27 Apr 2022 14:52:11 +0200 Subject: [PATCH 2/4] Replace null with empty function To keep collecting the tap position for targets. --- lib/src/widgets/animated_focus_light.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/animated_focus_light.dart b/lib/src/widgets/animated_focus_light.dart index a6edf71..20e9bce 100644 --- a/lib/src/widgets/animated_focus_light.dart +++ b/lib/src/widgets/animated_focus_light.dart @@ -195,7 +195,11 @@ class AnimatedStaticFocusLightState extends AnimatedFocusLightState { onTapDown: (details) { _tapHandlerForPosition(details); }, - onTap: _targetFocus.enableTargetTab ? () => _tapHandler(targetTap: true) : null, + onTap: _targetFocus.enableTargetTab + ? () => _tapHandler(targetTap: true) + + /// Essential for collecting [TapDownDetails]. Do not make [null] + : () {}, child: Container( color: Colors.transparent, width: (_targetPosition?.size.width ?? 0) + _getPaddingFocus() * 4, @@ -332,7 +336,11 @@ class AnimatedPulseFocusLightState extends AnimatedFocusLightState { top: (_targetPosition?.offset.dy ?? 0) - _getPaddingFocus() * 2, child: InkWell( borderRadius: _betBorderRadiusTarget(), - onTap: _targetFocus.enableTargetTab ? () => _tapHandler(targetTap: true) : null, + onTap: _targetFocus.enableTargetTab + ? () => _tapHandler(targetTap: true) + + /// Essential for collecting [TapDownDetails]. Do not make [null] + : () {}, onTapDown: (details) { _tapHandlerForPosition(details); }, From 87b2f16d519b37f693964fd3f0737a0231b005bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkay=20O=CC=88z?= Date: Wed, 27 Apr 2022 14:52:46 +0200 Subject: [PATCH 3/4] Update main.dart with new feature (onClickTargetWithTapPosition) --- example/lib/main.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index fe3aa02..3dd8b11 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -228,6 +228,10 @@ class _MyHomePageState extends State { onClickTarget: (target) { print('onClickTarget: $target'); }, + onClickTargetWithTapPosition: (target, tapDetails) { + print("target: $target"); + print("clicked at position local: ${tapDetails.localPosition} - global: ${tapDetails.globalPosition}"); + }, onClickOverlay: (target) { print('onClickOverlay: $target'); }, From 1278b9edfeab92bc59ded92d517e866abd22a859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berkay=20O=CC=88z?= Date: Wed, 27 Apr 2022 14:53:21 +0200 Subject: [PATCH 4/4] Update README.md with new feature + unFocusAnimationDuration --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 29994b3..09a3492 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,16 @@ void showTutorial() { // textSkip: "SKIP", // paddingFocus: 10, // focusAnimationDuration: Duration(milliseconds: 500), + // unFocusAnimationDuration: Duration(millisconds: 500), // pulseAnimationDuration: Duration(milliseconds: 500), // pulseVariation: Tween(begin: 1.0, end: 0.99), onFinish: (){ print("finish"); }, + onClickTargetWithTapPosition: (target, tapDetails) { + print("target: $target"); + print("clicked at position local: ${tapDetails.localPosition} - global: ${tapDetails.globalPosition}"); + }, onClickTarget: (target){ print(target); }, @@ -67,6 +72,7 @@ Attributes: | `alignSkip` | Alignment | use to align the skip in the target | | `paddingFocus` | Alignment | settings padding of the focus in target | | `focusAnimationDuration` | Duration | override the widget's global focus animation duration | +| `unFocusAnimationDuration` | Duration | override the widget's global unfocus animation duration | | `pulseVariation` | Tween | override interval pulse animation | ### Creating contents (ContentTarget) @@ -237,6 +243,10 @@ void showTutorial() { onClickTarget: (target){ print(target); }, + onClickTargetWithTapPosition: (target, tapDetails) { + print("target: $target"); + print("clicked at position local: ${tapDetails.localPosition} - global: ${tapDetails.globalPosition}"); + }, onClickOverlay: (target){ print(target); },