Repository: splashbyte/animated_toggle_switch

Issue:
[Feature request] Add the possibility to toggle off the switch
### Context
With `allowUnlistedValues: true`, we can now have initial value = null, and it means the switch doesn't have anything selected initially. But it's missing a use case: how to unselect the toggle (without adding an intermediate state of null when we only change from value1 to value 2)?

### Use case
I tried to use the `AnimatedToggleSwitch` with the following use case: clicking on the already selected value switches off the toggle (unselects everything). But I only want to change my state to null when I click on the already selected value. 

### What I tried
I tried to have the `onChanged` callback deal with this, but the `onChanged` callback is only called when the value is not the one we have currently selected. Then I tried with `onTap` callback, but because it doesn't have the currently selected value available, I cannot add a check for `myValue != null`. This is what I tried:
```dart
onChanged: (changed) { // this doesn't get triggered when the value has not changed
  setState(() => myValue = changed);
},
onTap: () {
  if (myValue != null) { // I cannot check what I have tapped, so this is the best I could do
    setState(() => myValue = null);
  }
}
```
### Result/issue
If I tap on the same value as selected, then it's working correctly (because `myValue` was selected, and it's now unselected). But if I tap on another value, the `onTap` is triggered, then the `onChanged` is triggered, so I get the intermediary state `myValue==null` that messes up my state.

### Summary
To sum up what I want to achieve VS what I get:
```mermaid
flowchart TB
  subgraph What I want
    direction TB
    U("value = null");
    U -->|"Click on 'A'"| A(value = A)
    A -->|"Click on 'A'"|U
    A -->|"Click on 'B'"| B("value = B")
  end
  subgraph What I get
    direction TB
    u("value = null");
    u -->|"Click on 'A'"| a(value = A)
    a -->|"Click on 'A'"| u
    a -->|"Click on 'B'"| b1("value = null\n(I want to get rid of this state)")
    b1 -->|"Then"|b2("value = B")
  end
```
### Feature request
So my feature request would be:
* either have the `onChanged` callback be triggered even when the value has not changed,
* or have a new callback for "toggling off" the value when `allowUnlistedValues == true`
* or even, have a parameter to the callback `onTap` to get what we value are currently tapping on

Please tell me if this is unclear.



Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c76b87..f1677b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.0-beta.6 (2023-08-22)
+
+- adds parameter to `onTap` ([#41](https://github.com/splashbyte/animated_toggle_switch/issues/41))
+
 ## 0.8.0-beta.5 (2023-08-18)
 
 - fixes `AnimationType.onHover`
diff --git a/example/lib/crazy_switch.dart b/example/lib/crazy_switch.dart
index 1fe3d35..063d40b 100644
--- a/example/lib/crazy_switch.dart
+++ b/example/lib/crazy_switch.dart
@@ -28,7 +28,7 @@ class _CrazySwitchState extends State<CrazySwitch> {
       animationDuration: const Duration(milliseconds: 350),
       animationCurve: Curves.bounceOut,
       iconBuilder: (context, local, global) => const SizedBox(),
-      onTap: () => setState(() => current = !current),
+      onTap: (_) => setState(() => current = !current),
       iconsTappable: false,
       onChanged: (b) => setState(() => current = b),
       height: height,
diff --git a/example/lib/main.dart b/example/lib/main.dart
index a7b5a37..cbd04e1 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -154,9 +154,9 @@ class _MyHomePageState extends State<MyHomePage> {
                         colors: [green, Colors.red[800]!],
                         stops: [
                           global.position -
-                              (1 - 2 * max(0, global.position - 0.5)) * 0.2,
+                              (1 - 2 * max(0, global.position - 0.5)) * 0.7,
                           global.position +
-                              max(0, 2 * (global.position - 0.5)) * 0.2,
+                              max(0, 2 * (global.position - 0.5)) * 0.7,
                         ],
                       ));
                     },
@@ -269,7 +269,7 @@ class _MyHomePageState extends State<MyHomePage> {
               Padding(
                 padding: const EdgeInsets.all(8.0),
                 child: Text(
-                  'Switch inspired by package lite_rolling_switch',
+                  'Switch similar to package lite_rolling_switch',
                   textAlign: TextAlign.center,
                 ),
               ),
@@ -300,10 +300,7 @@ class _MyHomePageState extends State<MyHomePage> {
                         CupertinoActivityIndicator(
                             color: Color.lerp(
                                 Colors.red[800], green, global.position)),
-                    onChanged: (b) {
-                      setState(() => positive = b);
-                      return Future<dynamic>.delayed(Duration(seconds: 2));
-                    },
+                    onChanged: (b) => setState(() => positive = b),
                     iconBuilder: (value) => value
                         ? Icon(Icons.power_outlined, color: green, size: 32.0)
                         : Icon(Icons.power_settings_new_rounded,
@@ -375,6 +372,7 @@ class _MyHomePageState extends State<MyHomePage> {
                 active: false,
                 current: value,
                 values: const [0, 1, 2, 3],
+                indicatorIconScale: sqrt2,
                 onChanged: (i) {
                   setState(() {
                     value = i;
@@ -423,6 +421,7 @@ class _MyHomePageState extends State<MyHomePage> {
                       )
                     ],
                   ),
+                  indicatorIconScale: sqrt2,
                   iconBuilder: coloredRollingIconBuilder,
                   borderWidth: 3.0,
                   styleAnimationType: AnimationType.onHover,
@@ -457,7 +456,11 @@ class _MyHomePageState extends State<MyHomePage> {
                 current: nullableValue,
                 allowUnlistedValues: true,
                 values: const [0, 1, 2, 3],
-                onTap: () => setState(() => nullableValue = null),
+                onTap: (info) {
+                  if (nullableValue == info.tapped?.value) {
+                    setState(() => nullableValue = null);
+                  }
+                },
                 onChanged: (i) => setState(() => nullableValue = i),
                 iconBuilder: rollingIconBuilder,
                 borderWidth: 4.5,
@@ -490,7 +493,7 @@ class _MyHomePageState extends State<MyHomePage> {
                   return const SizedBox();
                 },
                 cursors: ToggleCursors(defaultCursor: SystemMouseCursors.click),
-                onTap: () => setState(() => positive = !positive),
+                onTap: (_) => setState(() => positive = !positive),
                 iconsTappable: false,
                 wrapperBuilder: (context, global, child) {
                   return Stack(
@@ -703,6 +706,7 @@ class _MyHomePageState extends State<MyHomePage> {
                 current: value,
                 values: const [0, 1, 2, 3],
                 iconOpacity: 1.0,
+                selectedIconScale: 1.0,
                 indicatorSize: const Size.fromWidth(25),
                 foregroundIndicatorIconBuilder: (context, global) {
                   double pos = global.position;
diff --git a/lib/animated_toggle_switch.dart b/lib/animated_toggle_switch.dart
index 5eca8b4..fe0c859 100644
--- a/lib/animated_toggle_switch.dart
+++ b/lib/animated_toggle_switch.dart
@@ -8,11 +8,12 @@ import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 
-part 'src/animation_type_builder.dart';
+part 'src/animations.dart';
 part 'src/properties.dart';
 part 'src/style.dart';
 part 'src/tweens.dart';
 part 'src/cursors.dart';
+part 'src/widgets/animation_type_builder.dart';
 part 'src/foreground_indicator_transition.dart';
 part 'src/widgets/animated_toggle_switch.dart';
 part 'src/widgets/custom_animated_toggle_switch.dart';
diff --git a/lib/src/animations.dart b/lib/src/animations.dart
new file mode 100644
index 0000000..20529a1
--- /dev/null
+++ b/lib/src/animations.dart
@@ -0,0 +1,36 @@
+part of 'package:animated_toggle_switch/animated_toggle_switch.dart';
+
+// this Animation is not covered because it does not contain logic but
+// forwards all methods to its parent Animation.
+// coverage:ignore-start
+/// This class is a proxy for another animation.
+///
+/// It is used for passing animations in builders without exposing the real
+/// animation to the user.
+class _PrivateAnimation<T> extends Animation<T> {
+  final Animation<T> _parent;
+
+  _PrivateAnimation(this._parent);
+
+  @override
+  void addListener(VoidCallback listener) => _parent.addListener(listener);
+
+  @override
+  void addStatusListener(AnimationStatusListener listener) =>
+      _parent.addStatusListener(listener);
+
+  @override
+  void removeListener(VoidCallback listener) =>
+      _parent.removeListener(listener);
+
+  @override
+  void removeStatusListener(AnimationStatusListener listener) =>
+      _parent.removeStatusListener(listener);
+
+  @override
+  AnimationStatus get status => _parent.status;
+
+  @override
+  T get value => _parent.value;
+}
+// coverage:ignore-end
diff --git a/lib/src/properties.dart b/lib/src/properties.dart
index 773280f..b77298f 100644
--- a/lib/src/properties.dart
+++ b/lib/src/properties.dart
@@ -1,6 +1,17 @@
 // coverage:ignore-file
 part of 'package:animated_toggle_switch/animated_toggle_switch.dart';
 
+class ValueHolder<T> {
+  final T value;
+
+  /// The index of [value] in [values].
+  ///
+  /// If [values] does not contain [value], this value is set to [-1].
+  final int index;
+
+  ValueHolder({required this.value, required this.index});
+}
+
 class GlobalToggleProperties<T> {
   /// The position of the indicator relative to the indices of the values.
   final double position;
@@ -13,7 +24,7 @@ class GlobalToggleProperties<T> {
 
   /// The index of [current] in [values].
   ///
-  /// If [values] does not contain [current], this value is set to [-1].
+  /// If [values] does not contain [current], [currentIndex] is set to [-1].
   final int currentIndex;
 
   /// This value indicates if [values] does contain [current].
@@ -44,6 +55,17 @@ class GlobalToggleProperties<T> {
 
   final bool active;
 
+  /// This animation indicates whether the indicator is currently visible.
+  ///
+  /// [0.0] means it is not visible.
+  ///
+  /// [1.0] means it is fully visible.
+  ///
+  /// Depending on the curve of the animation, the value can also be below 0.0 or above 1.0.
+  ///
+  /// This will be publicly accessible in future releases.
+  final Animation<double> _indicatorAppearingAnimation;
+
   const GlobalToggleProperties({
     required this.position,
     required this.current,
@@ -55,7 +77,8 @@ class GlobalToggleProperties<T> {
     required this.mode,
     required this.loadingAnimationValue,
     required this.active,
-  });
+    required Animation<double> indicatorAppearingAnimation,
+  }) : _indicatorAppearingAnimation = indicatorAppearingAnimation;
 }
 
 class DetailedGlobalToggleProperties<T> extends GlobalToggleProperties<T> {
@@ -88,6 +111,7 @@ class DetailedGlobalToggleProperties<T> extends GlobalToggleProperties<T> {
     required super.mode,
     required super.loadingAnimationValue,
     required super.active,
+    required super.indicatorAppearingAnimation,
   });
 }
 
@@ -97,7 +121,7 @@ class LocalToggleProperties<T> {
 
   /// The index of [value].
   ///
-  /// If [values] does not contain [value], this value is set to [-1].
+  /// If [values] does not contain [value], this [index] is set to [-1].
   final int index;
 
   /// This value indicates if [values] does contain [value].
@@ -181,3 +205,46 @@ class SeparatorProperties<T> {
     required this.index,
   });
 }
+
+class TapProperties<T> {
+  /// Information about the point on which the user has tapped.
+  ///
+  /// This value can be [null] if the user taps on the border of an
+  /// [AnimatedToggleSwitch] or on the wrapper of a
+  /// [CustomAnimatedToggleSwitch].
+  final TapInfo<T>? tapped;
+
+  /// The values which are given to the switch.
+  ///
+  /// Helpful if the list is generated e.g.
+  /// when the switch constructor is called.
+  final List<T> values;
+
+  const TapProperties({
+    required this.tapped,
+    required this.values,
+  });
+}
+
+class TapInfo<T> {
+  /// The value that the user has tapped.
+  final T value;
+
+  /// The index of [value] in [values].
+  ///
+  /// [index == position.round()] should always be [true].
+  final int index;
+
+  /// The tapped position relative to the indices of the values.
+  ///
+  /// [position] can be in the interval from [-0.5] to [values.length - 0.5].
+  ///
+  /// [position.round() == index] should always be [true].
+  final double position;
+
+  TapInfo({
+    required this.value,
+    required this.index,
+    required this.position,
+  });
+}
diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart
index 10dd4ee..253cb88 100644
--- a/lib/src/widgets/animated_toggle_switch.dart
+++ b/lib/src/widgets/animated_toggle_switch.dart
@@ -72,7 +72,8 @@ abstract class _AnimatedToggleSwitchParent<T> extends StatelessWidget {
 /// If you want to implement a completely custom switch,
 /// you should use [CustomAnimatedToggleSwitch], which is used by
 /// [AnimatedToggleSwitch] in the background.
-class AnimatedToggleSwitch<T> extends _AnimatedToggleSwitchParent<T> {
+class AnimatedToggleSwitch<T extends Object?>
+    extends _AnimatedToggleSwitchParent<T> {
   /// The currently selected value. It has to be set at [onChanged] or whenever for animating to this value.
   ///
   /// [current] has to be in [values] for working correctly if [allowUnlistedValues] is false.
@@ -168,7 +169,7 @@ class AnimatedToggleSwitch<T> extends _AnimatedToggleSwitchParent<T> {
   final AnimationType indicatorAnimationType;
 
   /// Callback for tapping anywhere on the widget.
-  final TapCallback? onTap;
+  final TapCallback<T>? onTap;
 
   final IconArrangement _iconArrangement;
 
@@ -864,7 +865,7 @@ class AnimatedToggleSwitch<T> extends _AnimatedToggleSwitchParent<T> {
     this.styleAnimationType = AnimationType.onHover,
     this.indicatorAnimationType = AnimationType.onHover,
     this.fittingMode = FittingMode.preventHorizontalOverlapping,
-    Function()? onTap,
+    TapCallback<T>? onTap,
     this.minTouchTargetSize = 48.0,
     this.textDirection,
     this.cursors = const ToggleCursors(defaultCursor: SystemMouseCursors.click),
@@ -923,9 +924,9 @@ class AnimatedToggleSwitch<T> extends _AnimatedToggleSwitchParent<T> {
           iconList: null,
         );
 
-  static Function() _dualOnTap<T>(
+  static TapCallback<T> _dualOnTap<T>(
       ChangeCallback<T>? onChanged, List<T> values, T? current) {
-    return () =>
+    return (info) =>
         onChanged?.call(values.firstWhere((element) => element != current));
   }
 
diff --git a/lib/src/animation_type_builder.dart b/lib/src/widgets/animation_type_builder.dart
similarity index 81%
rename from lib/src/animation_type_builder.dart
rename to lib/src/widgets/animation_type_builder.dart
index 0448da2..a26f611 100644
--- a/lib/src/animation_type_builder.dart
+++ b/lib/src/widgets/animation_type_builder.dart
@@ -53,7 +53,6 @@ class _AnimationTypeHoverBuilderState<T, V>
     final values = widget.properties.values;
     final index1 = pos.floor();
     final index2 = pos.ceil();
-    final isListed = widget.properties.isCurrentListed;
     V listedValueFunction() => widget.lerp(
           widget.valueProvider(
               StyledToggleProperties(value: values[index1], index: index1)),
@@ -61,13 +60,13 @@ class _AnimationTypeHoverBuilderState<T, V>
               StyledToggleProperties(value: values[index2], index: index2)),
           pos - pos.floor(),
         );
-    final unlistedDoubleValue = isListed ? 0.0 : 1.0;
-    return TweenAnimationBuilder<double>(
-      duration: widget.indicatorAppearingDuration,
-      curve: widget.indicatorAppearingCurve,
-      tween: Tween(begin: unlistedDoubleValue, end: unlistedDoubleValue),
-      builder: (context, unlistedDoubleValue, _) {
-        if (unlistedDoubleValue == 0.0) {
+    final indicatorAppearingAnimation =
+        widget.properties._indicatorAppearingAnimation;
+    return AnimatedBuilder(
+      animation: indicatorAppearingAnimation,
+      builder: (context, _) {
+        final appearingValue = indicatorAppearingAnimation.value;
+        if (appearingValue >= 1.0) {
           return _EmptyWidget(
               key: _builderKey, child: widget.builder(listedValueFunction()));
         }
@@ -81,10 +80,10 @@ class _AnimationTypeHoverBuilderState<T, V>
             builder: (context, unlistedValue, _) {
               return _EmptyWidget(
                 key: _builderKey,
-                child: widget.builder(unlistedDoubleValue == 1.0
+                child: widget.builder(appearingValue <= 0.0
                     ? unlistedValue
-                    : widget.lerp(listedValueFunction(), unlistedValue,
-                        unlistedDoubleValue)),
+                    : widget.lerp(
+                        unlistedValue, listedValueFunction(), appearingValue)),
               );
             });
       },
diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart
index 51a80cb..84aef89 100644
--- a/lib/src/widgets/custom_animated_toggle_switch.dart
+++ b/lib/src/widgets/custom_animated_toggle_switch.dart
@@ -26,7 +26,7 @@ typedef IndicatorAppearingBuilder = Widget Function(
 
 typedef ChangeCallback<T> = FutureOr<void> Function(T value);
 
-typedef TapCallback = FutureOr<void> Function();
+typedef TapCallback<T> = FutureOr<void> Function(TapProperties<T> props);
 
 enum ToggleMode { animating, dragged, none }
 
@@ -59,7 +59,7 @@ enum IconArrangement {
 ///
 /// For pre-made switches, please use the constructors of [AnimatedToggleSwitch]
 /// instead.
-class CustomAnimatedToggleSwitch<T> extends StatefulWidget {
+class CustomAnimatedToggleSwitch<T extends Object?> extends StatefulWidget {
   /// The currently selected value. It has to be set at [onChanged] or whenever for animating to this value.
   ///
   /// [current] has to be in [values] for working correctly if [allowUnlistedValues] is false.
@@ -127,7 +127,7 @@ class CustomAnimatedToggleSwitch<T> extends StatefulWidget {
   final CustomSeparatorBuilder<T>? separatorBuilder;
 
   /// Callback for tapping anywhere on the widget.
-  final TapCallback? onTap;
+  final TapCallback<T>? onTap;
 
   /// Indicates if [onChanged] is called when an icon is tapped.
   ///
@@ -317,19 +317,12 @@ class _CustomAnimatedToggleSwitchState<T>
   void didUpdateWidget(covariant CustomAnimatedToggleSwitch<T> oldWidget) {
     super.didUpdateWidget(oldWidget);
     _checkForUnlistedValue();
-    if (oldWidget.indicatorAppearingDuration !=
-        widget.indicatorAppearingDuration) {
-      _appearingController.duration = widget.indicatorAppearingDuration;
-    }
-    if (oldWidget.indicatorAppearingCurve != widget.indicatorAppearingCurve) {
-      _appearingAnimation.curve = widget.indicatorAppearingCurve;
-    }
-    if (oldWidget.animationDuration != widget.animationDuration) {
-      _controller.duration = widget.animationDuration;
-    }
-    if (oldWidget.animationCurve != widget.animationCurve) {
-      _animation.curve = widget.animationCurve;
-    }
+
+    _appearingController.duration = widget.indicatorAppearingDuration;
+    _appearingAnimation.curve = widget.indicatorAppearingCurve;
+    _controller.duration = widget.animationDuration;
+    _animation.curve = widget.animationCurve;
+
     if (oldWidget.active && !widget.active) {
       _cancelDrag();
     }
@@ -364,9 +357,9 @@ class _CustomAnimatedToggleSwitchState<T>
 
   /// This method is called in two [GestureDetector]s because only one
   /// [GestureDetector.onTapUp] will be triggered.
-  void _onTap() {
+  void _onTap(TapProperties<T> info) {
     if (!_isActive) return;
-    final result = widget.onTap?.call();
+    final result = widget.onTap?.call(info);
     if (result is Future) {
       _addLoadingFuture(result);
     }
@@ -384,8 +377,8 @@ class _CustomAnimatedToggleSwitchState<T>
   /// IMPORTANT: This must be called in [didUpdateWidget] because it updates
   /// [_currentIndex] also.
   void _checkValuePosition() {
-    if (_animationInfo.toggleMode == ToggleMode.dragged) return;
     _currentIndex = widget.values.indexOf(widget.current);
+    if (_animationInfo.toggleMode == ToggleMode.dragged) return;
     if (_currentIndex >= 0) {
       _animateTo(_currentIndex);
     } else {
@@ -409,17 +402,17 @@ class _CustomAnimatedToggleSwitchState<T>
     return result;
   }
 
-  /// Returns the value index by the local position of the cursor.
+  /// Returns the [TapInfo] by the local position of the cursor.
   /// It is mainly intended as a helper function for the build method.
-  int _indexFromPosition(
+  TapInfo<T> _tapInfoFromPosition(
       double x, DetailedGlobalToggleProperties<T> properties) {
-    return _doubleFromPosition(x, properties).round();
-  }
-
-  /// Returns the value by the local position of the cursor.
-  /// It is mainly intended as a helper function for the build method.
-  T _valueFromPosition(double x, DetailedGlobalToggleProperties<T> properties) {
-    return widget.values[_indexFromPosition(x, properties)];
+    final position = _doubleFromPosition(x, properties);
+    final index = position.round();
+    return TapInfo(
+      value: widget.values[index],
+      index: index,
+      position: position,
+    );
   }
 
   @override
@@ -427,6 +420,8 @@ class _CustomAnimatedToggleSwitchState<T>
     double spacing = widget.spacing;
     final textDirection = _textDirectionOf(context);
     final loadingValue = _animationInfo.loading ? 1.0 : 0.0;
+    final privateIndicatorAppearingAnimation =
+        _PrivateAnimation(_appearingAnimation);
 
     final defaultCursor = !_isActive
         ? (_animationInfo.loading
@@ -440,9 +435,14 @@ class _CustomAnimatedToggleSwitchState<T>
     return SizedBox(
       height: widget.height,
       child: MouseRegion(
+        hitTestBehavior: HitTestBehavior.deferToChild,
         cursor: defaultCursor,
         child: GestureDetector(
-          onTapUp: (_) => _onTap(),
+          behavior: HitTestBehavior.deferToChild,
+          onTapUp: (_) => _onTap(TapProperties(
+            tapped: null,
+            values: widget.values,
+          )),
           child: TweenAnimationBuilder<double>(
             duration:
                 widget.loadingAnimationDuration ?? widget.animationDuration,
@@ -468,6 +468,8 @@ class _CustomAnimatedToggleSwitchState<T>
                     mode: _animationInfo.toggleMode,
                     loadingAnimationValue: loadingValue,
                     active: widget.active,
+                    indicatorAppearingAnimation:
+                        privateIndicatorAppearingAnimation,
                   );
                   Widget child = Padding(
                     padding: widget.padding,
@@ -579,6 +581,8 @@ class _CustomAnimatedToggleSwitchState<T>
                           mode: _animationInfo.toggleMode,
                           loadingAnimationValue: loadingValue,
                           active: widget.active,
+                          indicatorAppearingAnimation:
+                              privateIndicatorAppearingAnimation,
                         );
 
                         List<Widget> stack = <Widget>[
@@ -647,12 +651,17 @@ class _CustomAnimatedToggleSwitchState<T>
                                     behavior: HitTestBehavior.translucent,
                                     dragStartBehavior: DragStartBehavior.down,
                                     onTapUp: (details) {
-                                      _onTap();
-                                      if (!widget.iconsTappable) return;
-                                      T newValue = _valueFromPosition(
+                                      final tapInfo = _tapInfoFromPosition(
                                           details.localPosition.dx, properties);
-                                      if (newValue == widget.current) return;
-                                      _onChanged(newValue);
+                                      _onTap(TapProperties(
+                                        tapped: tapInfo,
+                                        values: widget.values,
+                                      ));
+                                      if (!widget.iconsTappable) return;
+                                      if (tapInfo.value == widget.current) {
+                                        return;
+                                      }
+                                      _onChanged(tapInfo.value);
                                     },
                                     onHorizontalDragStart: (details) {
                                       if (!isHoveringIndicator(
diff --git a/pubspec.lock b/pubspec.lock
index 798dcf2..1804eb8 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -223,10 +223,10 @@ packages:
     dependency: "direct dev"
     description:
       name: mocktail
-      sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53"
+      sha256: "9503969a7c2c78c7292022c70c0289ed6241df7a9ba720010c0b215af29a5a58"
       url: "https://pub.dev"
     source: hosted
-    version: "0.3.0"
+    version: "1.0.0"
   node_preamble:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 8af5fa4..affbd2c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: animated_toggle_switch
 description: Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations.
-version: 0.8.0-beta.5
+version: 0.8.0-beta.6
 repository: https://github.com/SplashByte/animated_toggle_switch
 issue_tracker: https://github.com/SplashByte/animated_toggle_switch/issues
 
@@ -16,7 +16,7 @@ dev_dependencies:
   flutter_lints: ^2.0.2
   flutter_test:
     sdk: flutter
-  mocktail: ^0.3.0
+  mocktail: ^1.0.0
 
 flutter:
 
@@ -27,3 +27,10 @@ funding:
 screenshots:
   - description: 'This image shows three examples of AnimatedToggleSwitch.'
     path: screenshots/preview.webp
+
+topics:
+  - ui
+  - switch
+  - toggle
+  - widget
+  - widgets


Test patch:
diff --git a/test/gesture_test.dart b/test/gesture_test.dart
index f87553b..1d84bd3 100644
--- a/test/gesture_test.dart
+++ b/test/gesture_test.dart
@@ -21,17 +21,29 @@ void main() {
         onChanged: changedFunction,
       ),
     ));
-    verifyNever(() => tapFunction.call());
+    verifyNever(() => tapFunction.call(any()));
     final currentFinder = find.byKey(iconKey(current));
     final nextFinder = find.byKey(iconKey(next));
+    final switchFinder = find.byType(AnimatedToggleSwitch<int>);
 
     await tester.tap(currentFinder, warnIfMissed: false);
-    verify(() => tapFunction()).called(1);
+    verify(() => tapFunction(any(
+        that: isA<TapProperties<int>>().having(
+            (i) => i.tapped?.value, 'tapped.value', current)))).called(1);
 
     await tester.tap(nextFinder, warnIfMissed: false);
     verify(() => changedFunction(next)).called(1);
-    verify(() => tapFunction()).called(1);
+    verify(() => tapFunction(any(
+        that: isA<TapProperties<int>>()
+            .having((i) => i.tapped?.value, 'tapped.value', next)))).called(1);
 
+    // tap on the border of the switch
+    await tester.tapAt(tester.getRect(switchFinder).centerLeft);
+    verify(() => tapFunction(any(
+        that: isA<TapProperties<int>>()
+            .having((i) => i.tapped, 'tapped', null)))).called(1);
+
+    verifyNoMoreInteractions(tapFunction);
     verifyNoMoreInteractions(changedFunction);
   }, testDual: false);
 
@@ -109,15 +121,15 @@ void main() {
         iconsTappable: false,
       ),
     ));
-    verifyNever(() => tapFunction.call());
+    verifyNever(() => tapFunction.call(any()));
     final currentFinder = find.byKey(iconKey(current));
     final nextFinder = find.byKey(iconKey(next));
 
     await tester.tap(currentFinder, warnIfMissed: false);
-    verify(() => tapFunction()).called(1);
+    verify(() => tapFunction(any())).called(1);
 
     await tester.tap(nextFinder, warnIfMissed: false);
-    verify(() => tapFunction()).called(1);
+    verify(() => tapFunction(any())).called(1);
 
     verifyNoMoreInteractions(changedFunction);
   }, testDual: false);
diff --git a/test/helper.dart b/test/helper.dart
index 5a6401b..d97e175 100644
--- a/test/helper.dart
+++ b/test/helper.dart
@@ -3,6 +3,7 @@ import 'dart:async';
 import 'package:animated_toggle_switch/animated_toggle_switch.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:mocktail/mocktail.dart';
 
 import 'keys.dart';
 
@@ -75,7 +76,7 @@ typedef SwitchBuilder<T> = AnimatedToggleSwitch<T> Function({
   List<Widget>? iconList,
   TextDirection? textDirection,
   ChangeCallback<T>? onChanged,
-  TapCallback? onTap,
+  TapCallback<T>? onTap,
   bool? loading,
   bool allowUnlistedValues,
   ToggleStyle? style,
@@ -95,7 +96,7 @@ typedef SimpleSwitchBuilder<T> = AnimatedToggleSwitch<T> Function({
   List<Widget>? iconList,
   TextDirection? textDirection,
   ChangeCallback<T>? onChanged,
-  TapCallback? onTap,
+  TapCallback<T>? onTap,
   bool? loading,
   bool allowUnlistedValues,
   ToggleStyle? style,
@@ -122,6 +123,11 @@ void defaultTestAllSwitches(
   bool testCustom = true,
   bool testSize = true,
 }) {
+  registerFallbackValue(const TapProperties<int>(
+    tapped: null,
+    values: [],
+  ));
+
   testAllSwitches<int>(
     description,
     (tester, buildSwitch, type) => test(
@@ -132,7 +138,7 @@ void defaultTestAllSwitches(
         List<Widget>? iconList,
         TextDirection? textDirection,
         ChangeCallback<int>? onChanged,
-        TapCallback? onTap,
+        TapCallback<int>? onTap,
         bool? loading,
         bool allowUnlistedValues = false,
         ToggleStyle? style,
@@ -183,7 +189,7 @@ void defaultTestAllSwitches(
           List<Widget>? iconList,
           TextDirection? textDirection,
           ChangeCallback<int>? onChanged,
-          TapCallback? onTap,
+          TapCallback<int>? onTap,
           bool? loading,
           bool allowUnlistedValues = false,
           ToggleStyle? style,
@@ -246,7 +252,7 @@ void testAllSwitches<T>(
               List<Widget>? iconList,
               TextDirection? textDirection,
               ChangeCallback<T>? onChanged,
-              TapCallback? onTap,
+              TapCallback<T>? onTap,
               bool? loading,
               bool allowUnlistedValues = false,
               ToggleStyle? style,
@@ -295,7 +301,7 @@ void testAllSwitches<T>(
               List<Widget>? iconList,
               TextDirection? textDirection,
               ChangeCallback<T>? onChanged,
-              TapCallback? onTap,
+              TapCallback<T>? onTap,
               bool? loading,
               bool allowUnlistedValues = false,
               ToggleStyle? style,
@@ -348,7 +354,7 @@ void testAllSwitches<T>(
                 List<Widget>? iconList,
                 TextDirection? textDirection,
                 ChangeCallback<T>? onChanged,
-                TapCallback? onTap,
+                TapCallback<T>? onTap,
                 bool? loading,
                 bool allowUnlistedValues = false,
                 ToggleStyle? style,
@@ -399,7 +405,7 @@ void testAllSwitches<T>(
                 List<Widget>? iconList,
                 TextDirection? textDirection,
                 ChangeCallback<T>? onChanged,
-                TapCallback? onTap,
+                TapCallback<T>? onTap,
                 bool? loading,
                 bool allowUnlistedValues = false,
                 ToggleStyle? style,
@@ -457,7 +463,7 @@ void testAllSwitches<T>(
                 List<Widget>? iconList,
                 TextDirection? textDirection,
                 ChangeCallback<T>? onChanged,
-                TapCallback? onTap,
+                TapCallback<T>? onTap,
                 bool? loading,
                 bool allowUnlistedValues = false,
                 ToggleStyle? style,
@@ -507,7 +513,7 @@ void testAllSwitches<T>(
                 List<Widget>? iconList,
                 TextDirection? textDirection,
                 ChangeCallback<T>? onChanged,
-                TapCallback? onTap,
+                TapCallback<T>? onTap,
                 bool? loading,
                 bool allowUnlistedValues = false,
                 ToggleStyle? style,
diff --git a/test/loading_test.dart b/test/loading_test.dart
index 6e6938a..633a3a3 100644
--- a/test/loading_test.dart
+++ b/test/loading_test.dart
@@ -17,7 +17,7 @@ void main() {
       child: buildSwitch(
         current: current,
         iconBuilder: iconBuilder,
-        onTap: () => Future<dynamic>.delayed(loadingDuration),
+        onTap: (info) => Future<dynamic>.delayed(loadingDuration),
         onChanged: (_) => Future<dynamic>.delayed(loadingDuration),
       ),
     ));
@@ -107,7 +107,7 @@ void main() {
       child: buildSwitch(
         current: current,
         iconBuilder: iconBuilder,
-        onTap: () => Future<dynamic>.delayed(loadingDuration),
+        onTap: (info) => Future<dynamic>.delayed(loadingDuration),
         onChanged: (_) => Future<dynamic>.delayed(loadingDuration),
         loading: false,
       ),
diff --git a/test/mocks.dart b/test/mocks.dart
index 20794ac..b56c802 100644
--- a/test/mocks.dart
+++ b/test/mocks.dart
@@ -1,14 +1,15 @@
+import 'package:animated_toggle_switch/animated_toggle_switch.dart';
 import 'package:mocktail/mocktail.dart';
 
-abstract class TestFunction {
-  void call();
+abstract class TestOnTapFunction<T> {
+  void call(TapProperties<T> props);
 }
 
 abstract class TestOnChangedFunction<T> {
   void call(T value);
 }
 
-class MockFunction extends Mock implements TestFunction {}
+class MockFunction<T> extends Mock implements TestOnTapFunction<T> {}
 
 class MockOnChangedFunction<T> extends Mock
     implements TestOnChangedFunction<T> {}