LCOV - code coverage report
Current view: top level - input - increment_decrement_widget.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 73 85 85.9 %
Date: 2024-11-26 10:38:40 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : 
       3             : import 'package:flutter/material.dart';
       4             : 
       5             : import '../buttons/custom_action_button.dart';
       6             : 
       7             : /// A type definition for a callback function that updates the quantity value.
       8             : ///
       9             : /// The [ValueUpdate] function takes an integer parameter [updateValue] representing
      10             : /// the new quantity and returns a dynamic value. This allows flexibility in handling
      11             : /// the updated value, whether it's immediately applied or processed asynchronously.
      12             : typedef ValueUpdate = dynamic Function(int updateValue);
      13             : 
      14             : /// A widget that provides increment and decrement functionality with customizable
      15             : /// buttons and display. It allows users to increase or decrease a numerical
      16             : /// value within optional bounds, making it suitable for quantity selectors in
      17             : /// shopping carts, forms, and other interactive UI components.
      18             : ///
      19             : /// The [IncrementDecrementWidget] offers multiple factory constructors to cater
      20             : /// to different design requirements, such as flat, raised, minimal, and squared
      21             : /// styles. It also supports long-press actions for continuous incrementing or
      22             : /// decrementing.
      23             : class IncrementDecrementWidget extends StatefulWidget {
      24             :   // Quantity properties
      25             : 
      26             :   /// The initial quantity to display.
      27             :   ///
      28             :   /// Must be a non-negative integer. Represents the current value of the widget.
      29             :   final int quantity;
      30             : 
      31             :   /// The maximum allowed quantity.
      32             :   ///
      33             :   /// If provided, the quantity cannot exceed this value. If `null`, there is no upper bound.
      34             :   final int? maxQuantity;
      35             : 
      36             :   /// The minimum allowed quantity.
      37             :   ///
      38             :   /// If provided, the quantity cannot go below this value. If `null`, there is no lower bound.
      39             :   final int? minValue;
      40             : 
      41             :   // Callback functions
      42             : 
      43             :   /// A callback function that is invoked whenever the quantity changes.
      44             :   ///
      45             :   /// It receives the updated quantity as an integer. This callback can return
      46             :   /// a new integer value to override the incremented or decremented value.
      47             :   /// It supports both synchronous and asynchronous operations.
      48             :   final ValueUpdate? onChanged;
      49             : 
      50             :   // Customization properties
      51             : 
      52             :   /// The background color of the widget.
      53             :   ///
      54             :   /// Applies to the area surrounding the buttons and the quantity display.
      55             :   final Color? backgroundColor;
      56             : 
      57             :   /// The color of the widget's icons.
      58             :   ///
      59             :   /// Applies to both increment and decrement icons. If not specified, defaults to the theme's icon color.
      60             :   final Color? iconColor;
      61             : 
      62             :   /// The elevation (shadow depth) of the widget.
      63             :   ///
      64             :   /// A higher value increases the shadow's prominence. Defaults vary based on the factory constructor used.
      65             :   final double? elevation;
      66             : 
      67             :   /// The external margin around the widget.
      68             :   ///
      69             :   /// Adds space outside the widget's boundaries.
      70             :   final EdgeInsetsGeometry? margin;
      71             : 
      72             :   /// The padding inside the quantity display.
      73             :   ///
      74             :   /// Controls the space between the quantity text and its container.
      75             :   final EdgeInsetsGeometry? valuePadding;
      76             : 
      77             :   /// The text style for displaying the quantity.
      78             :   ///
      79             :   /// If not provided, defaults to the theme's `titleLarge` text style.
      80             :   final TextStyle? quantityTextStyle;
      81             : 
      82             :   /// The border radius of the widget's buttons.
      83             :   ///
      84             :   /// Controls the roundness of the buttons' corners. Defaults vary based on the factory constructor used.
      85             :   final double? borderRadius;
      86             : 
      87             :   /// The width of the entire widget.
      88             :   ///
      89             :   /// If not specified, the widget adapts to its parent constraints.
      90             :   final double? width;
      91             : 
      92             :   /// The height of the entire widget.
      93             :   ///
      94             :   /// If not specified, the widget adapts to its content.
      95             :   final double? height;
      96             : 
      97             :   /// The padding inside the increment and decrement buttons.
      98             :   ///
      99             :   /// Controls the space between the button's icon and its boundaries.
     100             :   final EdgeInsetsGeometry? buttonPadding;
     101             : 
     102             :   /// The external margin around each increment and decrement button.
     103             :   ///
     104             :   /// Adds space outside the buttons' boundaries.
     105             :   final EdgeInsetsGeometry? buttonMargin;
     106             : 
     107             :   /// The width of the increment and decrement buttons.
     108             :   ///
     109             :   /// If not specified, buttons adapt to their content.
     110             :   final double? buttonWidth;
     111             : 
     112             :   /// The height of the increment and decrement buttons.
     113             :   ///
     114             :   /// If not specified, buttons adapt to their content.
     115             :   final double? buttonHeight;
     116             : 
     117             :   // CustomActionButton parameters
     118             : 
     119             :   /// The splash color of the buttons when tapped.
     120             :   ///
     121             :   /// Defines the color of the ripple effect upon interaction.
     122             :   final Color? splashColor;
     123             : 
     124             :   /// The border color of the buttons.
     125             :   ///
     126             :   /// If specified, buttons will have a border with this color.
     127             :   final Color? borderColor;
     128             : 
     129             :   /// The splash factory to define interaction effects.
     130             :   ///
     131             :   /// Allows customization of the splash effect (e.g., ripple type).
     132             :   final InteractiveInkFeatureFactory? splashFactory;
     133             : 
     134             :   // Factory-specific properties
     135             : 
     136             :   /// The icon widget for the increment button.
     137             :   ///
     138             :   /// If not provided, defaults to a plus icon.
     139             :   final Widget? incrementIcon;
     140             : 
     141             :   /// The icon widget for the decrement button.
     142             :   ///
     143             :   /// If not provided, defaults to a minus icon.
     144             :   final Widget? decrementIcon;
     145             : 
     146             :   // Button shape
     147             : 
     148             :   /// The shape of the increment and decrement buttons.
     149             :   ///
     150             :   /// Allows for customizing the buttons' outline and borders.
     151             :   final OutlinedBorder? buttonShape;
     152             : 
     153             :   // Long-press settings
     154             : 
     155             :   /// The interval between repeated increment/decrement actions during a long press.
     156             :   ///
     157             :   /// Determines how quickly the quantity changes when the button is held down.
     158             :   final Duration longPressInterval;
     159             : 
     160             :   // Alignment
     161             : 
     162             :   /// The alignment of the buttons and quantity display within the widget.
     163             :   ///
     164             :   /// Controls how the child widgets are placed along the main axis (horizontal).
     165             :   final MainAxisAlignment? alignment;
     166             : 
     167             :   /// Creates an [IncrementDecrementWidget] with the specified properties.
     168             :   ///
     169             :   /// The [quantity] parameter is required and sets the initial value.
     170           1 :   const IncrementDecrementWidget({
     171             :     super.key,
     172             :     required this.quantity,
     173             :     this.maxQuantity,
     174             :     this.minValue,
     175             :     this.onChanged,
     176             :     this.backgroundColor,
     177             :     this.iconColor,
     178             :     this.elevation,
     179             :     this.margin,
     180             :     this.valuePadding,
     181             :     this.quantityTextStyle,
     182             :     this.borderRadius,
     183             :     this.width,
     184             :     this.height,
     185             :     this.buttonPadding,
     186             :     this.buttonMargin,
     187             :     this.buttonWidth,
     188             :     this.buttonHeight,
     189             :     this.splashColor,
     190             :     this.borderColor,
     191             :     this.splashFactory,
     192             :     this.incrementIcon,
     193             :     this.decrementIcon,
     194             :     this.buttonShape,
     195             :     this.longPressInterval = const Duration(milliseconds: 100),
     196             :     this.alignment,
     197             :   });
     198             : 
     199             :   /// Factory constructor for a flat design.
     200             :   ///
     201             :   /// The flat design has no elevation and transparent background by default.
     202             :   ///
     203             :   /// Example usage:
     204             :   /// ```dart
     205             :   /// IncrementDecrementWidget.flat(
     206             :   ///   quantity: 1,
     207             :   ///   maxQuantity: 10,
     208             :   ///   onChanged: (newValue) {
     209             :   ///     // Handle value change
     210             :   ///   },
     211             :   /// );
     212             :   /// ```
     213           0 :   factory IncrementDecrementWidget.flat({
     214             :     required int quantity,
     215             :     int? maxQuantity,
     216             :     int? minValue,
     217             :     ValueUpdate? onChanged,
     218             :     Color? backgroundColor,
     219             :     Color? iconColor,
     220             :     EdgeInsetsGeometry? margin,
     221             :     EdgeInsetsGeometry? valuePadding,
     222             :     EdgeInsetsGeometry? buttonMargin,
     223             :     EdgeInsetsGeometry? buttonPadding,
     224             :     TextStyle? quantityTextStyle,
     225             :     double? borderRadius,
     226             :     double? width,
     227             :     double? height,
     228             :     double? buttonWidth,
     229             :     double? buttonHeight,
     230             :     Color? splashColor,
     231             :     Color? borderColor,
     232             :     InteractiveInkFeatureFactory? splashFactory,
     233             :     Widget? incrementIcon,
     234             :     Widget? decrementIcon,
     235             :     Duration longPressInterval = const Duration(milliseconds: 100),
     236             :     MainAxisAlignment? alignment,
     237             :   }) {
     238           0 :     return IncrementDecrementWidget(
     239             :       quantity: quantity,
     240             :       maxQuantity: maxQuantity,
     241             :       minValue: minValue,
     242             :       onChanged: onChanged,
     243             :       backgroundColor: backgroundColor ?? Colors.transparent,
     244             :       iconColor: iconColor,
     245             :       elevation: 0.0,
     246             :       margin: margin,
     247             :       valuePadding: valuePadding,
     248             :       buttonMargin: buttonMargin,
     249             :       buttonPadding: buttonPadding,
     250             :       quantityTextStyle: quantityTextStyle,
     251             :       borderRadius: borderRadius ?? 10.0,
     252             :       width: width,
     253             :       height: height,
     254             :       buttonWidth: buttonWidth,
     255             :       buttonHeight: buttonHeight,
     256             :       splashColor: splashColor,
     257             :       borderColor: borderColor,
     258             :       splashFactory: splashFactory,
     259             :       incrementIcon: incrementIcon,
     260             :       decrementIcon: decrementIcon,
     261             :       longPressInterval: longPressInterval,
     262             :       alignment: alignment,
     263             :     );
     264             :   }
     265             : 
     266             :   /// Factory constructor for a raised design.
     267             :   ///
     268             :   /// The raised design has elevation and an optional background color.
     269             :   ///
     270             :   /// Example usage:
     271             :   /// ```dart
     272             :   /// IncrementDecrementWidget.raised(
     273             :   ///   quantity: 2,
     274             :   ///   maxQuantity: 5,
     275             :   ///   onChanged: (newValue) {
     276             :   ///     // Handle value change
     277             :   ///   },
     278             :   /// );
     279             :   /// ```
     280           0 :   factory IncrementDecrementWidget.raised({
     281             :     required int quantity,
     282             :     int? maxQuantity,
     283             :     int? minValue,
     284             :     ValueUpdate? onChanged,
     285             :     Color? backgroundColor,
     286             :     Color? iconColor,
     287             :     double? elevation,
     288             :     EdgeInsetsGeometry? margin,
     289             :     EdgeInsetsGeometry? valuePadding,
     290             :     EdgeInsetsGeometry? buttonMargin,
     291             :     EdgeInsetsGeometry? buttonPadding,
     292             :     TextStyle? quantityTextStyle,
     293             :     double? borderRadius,
     294             :     double? width,
     295             :     double? height,
     296             :     double? buttonWidth,
     297             :     double? buttonHeight,
     298             :     Color? borderColor,
     299             :     Widget? incrementIcon,
     300             :     Widget? decrementIcon,
     301             :     Duration longPressInterval = const Duration(milliseconds: 100),
     302             :     MainAxisAlignment? alignment,
     303             :   }) {
     304           0 :     return IncrementDecrementWidget(
     305             :       quantity: quantity,
     306             :       maxQuantity: maxQuantity,
     307             :       minValue: minValue,
     308             :       onChanged: onChanged,
     309             :       backgroundColor: backgroundColor,
     310             :       iconColor: iconColor,
     311             :       elevation: elevation ?? 6.0,
     312             :       margin: margin,
     313             :       valuePadding: valuePadding,
     314             :       buttonMargin: buttonMargin,
     315             :       buttonPadding: buttonPadding,
     316             :       quantityTextStyle: quantityTextStyle,
     317             :       borderRadius: borderRadius ?? 10.0,
     318             :       width: width,
     319             :       height: height,
     320             :       buttonWidth: buttonWidth,
     321             :       buttonHeight: buttonHeight,
     322             :       borderColor: borderColor,
     323             :       splashFactory: NoSplash.splashFactory,
     324             :       splashColor: Colors.transparent,
     325             :       incrementIcon: incrementIcon,
     326             :       decrementIcon: decrementIcon,
     327             :       longPressInterval: longPressInterval,
     328             :       alignment: alignment,
     329             :     );
     330             :   }
     331             : 
     332             :   /// Factory constructor for a minimalistic design.
     333             :   ///
     334             :   /// The minimal design has no elevation and transparent background by default,
     335             :   /// focusing on simplicity and compactness.
     336             :   ///
     337             :   /// Example usage:
     338             :   /// ```dart
     339             :   /// IncrementDecrementWidget.minimal(
     340             :   ///   quantity: 3,
     341             :   ///   minValue: 1,
     342             :   ///   onChanged: (newValue) {
     343             :   ///     // Handle value change
     344             :   ///   },
     345             :   /// );
     346             :   /// ```
     347           0 :   factory IncrementDecrementWidget.minimal({
     348             :     required int quantity,
     349             :     int? maxQuantity,
     350             :     int? minValue,
     351             :     ValueUpdate? onChanged,
     352             :     Color? iconColor,
     353             :     EdgeInsetsGeometry? margin,
     354             :     EdgeInsetsGeometry? valuePadding,
     355             :     EdgeInsetsGeometry? buttonMargin,
     356             :     EdgeInsetsGeometry? buttonPadding,
     357             :     TextStyle? quantityTextStyle,
     358             :     double? width,
     359             :     double? height,
     360             :     double? buttonWidth,
     361             :     double? buttonHeight,
     362             :     Widget? incrementIcon,
     363             :     Widget? decrementIcon,
     364             :     Duration longPressInterval = const Duration(milliseconds: 100),
     365             :     MainAxisAlignment? alignment,
     366             :   }) {
     367           0 :     return IncrementDecrementWidget(
     368             :       quantity: quantity,
     369             :       maxQuantity: maxQuantity,
     370             :       minValue: minValue,
     371             :       onChanged: onChanged,
     372             :       backgroundColor: Colors.transparent,
     373             :       iconColor: iconColor,
     374             :       elevation: 0.0,
     375             :       margin: margin,
     376             :       valuePadding: valuePadding,
     377             :       buttonMargin: buttonMargin,
     378             :       buttonPadding: buttonPadding,
     379             :       quantityTextStyle: quantityTextStyle,
     380             :       width: width,
     381             :       height: height,
     382             :       buttonWidth: buttonWidth,
     383             :       buttonHeight: buttonHeight,
     384             :       splashFactory: NoSplash.splashFactory,
     385             :       splashColor: Colors.transparent,
     386             :       incrementIcon: incrementIcon,
     387             :       decrementIcon: decrementIcon,
     388             :       longPressInterval: longPressInterval,
     389             :       alignment: alignment,
     390             :     );
     391             :   }
     392             : 
     393             :   /// Factory constructor for squared buttons with equal width and height
     394             :   /// and customizable border radius.
     395             :   ///
     396             :   /// The squared design ensures that the increment and decrement buttons are
     397             :   /// square-shaped, with equal width and height, suitable for compact layouts.
     398             :   ///
     399             :   /// Example usage:
     400             :   /// ```dart
     401             :   /// IncrementDecrementWidget.squared(
     402             :   ///   quantity: 4,
     403             :   ///   maxQuantity: 8,
     404             :   ///   onChanged: (newValue) {
     405             :   ///     // Handle value change
     406             :   ///   },
     407             :   /// );
     408             :   /// ```
     409           0 :   factory IncrementDecrementWidget.squared({
     410             :     required int quantity,
     411             :     int? maxQuantity,
     412             :     int? minValue,
     413             :     ValueUpdate? onChanged,
     414             :     Color? backgroundColor,
     415             :     Color? iconColor,
     416             :     double? elevation,
     417             :     EdgeInsetsGeometry? margin,
     418             :     EdgeInsetsGeometry? valuePadding,
     419             :     EdgeInsetsGeometry? buttonMargin,
     420             :     EdgeInsetsGeometry? buttonPadding,
     421             :     TextStyle? quantityTextStyle,
     422             :     double? width,
     423             :     double? height,
     424             :     double? buttonSize,
     425             :     double? borderRadius, // Allow specifying border radius
     426             :     Color? borderColor,
     427             :     Widget? incrementIcon,
     428             :     Widget? decrementIcon,
     429             :     Duration longPressInterval = const Duration(milliseconds: 100),
     430             :     MainAxisAlignment? alignment,
     431             :   }) {
     432             :     final double size = buttonSize ?? 50.0;
     433             :     final double effectiveBorderRadius = borderRadius ?? 10.0;
     434           0 :     return IncrementDecrementWidget(
     435             :       quantity: quantity,
     436             :       maxQuantity: maxQuantity,
     437             :       minValue: minValue,
     438             :       onChanged: onChanged,
     439             :       backgroundColor: backgroundColor,
     440             :       iconColor: iconColor,
     441             :       elevation: elevation ?? 0.0,
     442             :       margin: margin,
     443             :       valuePadding: valuePadding,
     444             :       buttonMargin: buttonMargin,
     445             :       buttonPadding: buttonPadding,
     446             :       quantityTextStyle: quantityTextStyle,
     447             :       width: width,
     448             :       height: height,
     449             :       buttonWidth: size,
     450             :       buttonHeight: size,
     451             :       borderColor: borderColor,
     452             :       borderRadius: effectiveBorderRadius,
     453             :       // Set the border radius
     454           0 :       splashColor: Colors.grey.withOpacity(0.2),
     455             :       incrementIcon: incrementIcon,
     456             :       decrementIcon: decrementIcon,
     457           0 :       buttonShape: RoundedRectangleBorder(
     458           0 :         borderRadius: BorderRadius.circular(effectiveBorderRadius),
     459             :       ),
     460             :       longPressInterval: longPressInterval,
     461             :       alignment: alignment ?? MainAxisAlignment.center,
     462             :     );
     463             :   }
     464             : 
     465           1 :   @override
     466             :   State<IncrementDecrementWidget> createState() =>
     467           1 :       _IncrementDecrementWidgetState();
     468             : }
     469             : 
     470             : class _IncrementDecrementWidgetState extends State<IncrementDecrementWidget> {
     471             :   /// The current quantity value displayed by the widget.
     472             :   ///
     473             :   /// Initialized with the [widget.quantity] value.
     474             :   late int _currentQuantity;
     475             : 
     476             :   /// Initializes the state and sets the initial quantity.
     477           1 :   @override
     478             :   void initState() {
     479           1 :     super.initState();
     480           3 :     _currentQuantity = widget.quantity;
     481             :   }
     482             : 
     483             :   /// Increments the quantity by one, respecting the [maxQuantity] if set.
     484             :   ///
     485             :   /// Invokes the [onChanged] callback with the updated value, allowing for
     486             :   /// custom handling or validation. Updates the UI with the new quantity.
     487           1 :   Future<void> _increment() async {
     488           6 :     if (widget.maxQuantity == null || _currentQuantity < widget.maxQuantity!) {
     489           2 :       int updatedQuantity = _currentQuantity + 1;
     490             :       int newQuantity = updatedQuantity;
     491             : 
     492           2 :       if (widget.onChanged != null) {
     493           3 :         var result = widget.onChanged!(updatedQuantity);
     494             : 
     495           1 :         if (result is Future<int?>) {
     496             :           newQuantity = await result ?? updatedQuantity;
     497           1 :         } else if (result is int?) {
     498             :           newQuantity = result ?? updatedQuantity;
     499             :         }
     500             :         // If result is void (null), use updatedQuantity
     501             :       }
     502             : 
     503           2 :       setState(() {
     504           1 :         _currentQuantity = newQuantity;
     505             :       });
     506             :     }
     507             :   }
     508             : 
     509             :   /// Decrements the quantity by one, respecting the [minValue] if set.
     510             :   ///
     511             :   /// Invokes the [onChanged] callback with the updated value, allowing for
     512             :   /// custom handling or validation. Updates the UI with the new quantity.
     513           1 :   Future<void> _decrement() async {
     514           6 :     if (widget.minValue == null || _currentQuantity > widget.minValue!) {
     515           2 :       int updatedQuantity = _currentQuantity - 1;
     516             :       int newQuantity = updatedQuantity;
     517             : 
     518           2 :       if (widget.onChanged != null) {
     519           3 :         var result = widget.onChanged!(updatedQuantity);
     520             : 
     521           1 :         if (result is Future<int?>) {
     522             :           newQuantity = await result ?? updatedQuantity;
     523           1 :         } else if (result is int?) {
     524             :           newQuantity = result ?? updatedQuantity;
     525             :         }
     526             :         // If result is void (null), use updatedQuantity
     527             :       }
     528             : 
     529           2 :       setState(() {
     530           1 :         _currentQuantity = newQuantity;
     531             :       });
     532             :     }
     533             :   }
     534             : 
     535             :   /// Builds the increment and decrement buttons along with the quantity display.
     536             :   ///
     537             :   /// Utilizes [CustomActionButton.longPress] to handle both tap and long-press
     538             :   /// interactions for continuous incrementing or decrementing.
     539           1 :   @override
     540             :   Widget build(BuildContext context) {
     541             :     final Color effectiveBackgroundColor =
     542           4 :         widget.backgroundColor ?? Theme.of(context).cardColor;
     543             :     final EdgeInsetsGeometry valuePadding =
     544           2 :         widget.valuePadding ?? const EdgeInsets.symmetric(horizontal: 10);
     545             :     final EdgeInsetsGeometry buttonMargin =
     546           2 :         widget.buttonMargin ?? EdgeInsets.zero;
     547           2 :     final double? effectiveWidth = widget.width;
     548             :     final MainAxisAlignment effectiveAlignment =
     549           2 :         widget.alignment ?? MainAxisAlignment.spaceBetween;
     550             : 
     551           1 :     return SizedBox(
     552             :       width: effectiveWidth,
     553           1 :       child: Row(
     554             :         mainAxisAlignment: effectiveAlignment,
     555           1 :         children: <Widget>[
     556           1 :           _buildActionButton(
     557             :             context,
     558           2 :             widget.decrementIcon ?? const Icon(Icons.remove),
     559             :             onPressed:
     560           6 :                 (widget.minValue == null || _currentQuantity > widget.minValue!)
     561           1 :                     ? _decrement
     562             :                     : null,
     563             :             isEnabled:
     564           6 :                 widget.minValue == null || _currentQuantity > widget.minValue!,
     565           1 :             onLongPress: _decrement,
     566             :             effectiveBackgroundColor: effectiveBackgroundColor,
     567             :             effectiveMargin: buttonMargin,
     568           2 :             effectiveWidth: widget.buttonWidth,
     569           2 :             effectiveHeight: widget.buttonHeight,
     570             :           ),
     571           1 :           _buildQuantityDisplay(context, valuePadding),
     572           1 :           _buildActionButton(
     573             :             context,
     574           2 :             widget.incrementIcon ?? const Icon(Icons.add),
     575           2 :             onPressed: (widget.maxQuantity == null ||
     576           4 :                     _currentQuantity < widget.maxQuantity!)
     577           1 :                 ? _increment
     578             :                 : null,
     579           2 :             isEnabled: widget.maxQuantity == null ||
     580           4 :                 _currentQuantity < widget.maxQuantity!,
     581           1 :             onLongPress: _increment,
     582             :             effectiveBackgroundColor: effectiveBackgroundColor,
     583             :             effectiveMargin: buttonMargin,
     584           2 :             effectiveWidth: widget.buttonWidth,
     585           2 :             effectiveHeight: widget.buttonHeight,
     586             :           ),
     587             :         ],
     588             :       ),
     589             :     );
     590             :   }
     591             : 
     592             :   /// Builds an individual action button (increment or decrement).
     593             :   ///
     594             :   /// Configures the button's appearance and behavior based on its enabled state.
     595             :   ///
     596             :   /// - [icon]: The icon to display on the button.
     597             :   /// - [onPressed]: The callback to invoke when the button is tapped.
     598             :   /// - [isEnabled]: Determines if the button is active.
     599             :   /// - [onLongPress]: The callback to invoke on a long press.
     600             :   /// - [effectiveBackgroundColor]: The background color of the button.
     601             :   /// - [effectiveMargin]: The external margin around the button.
     602             :   /// - [effectiveWidth]: The width of the button.
     603             :   /// - [effectiveHeight]: The height of the button.
     604           1 :   Widget _buildActionButton(
     605             :     BuildContext context,
     606             :     Widget icon, {
     607             :     required VoidCallback? onPressed,
     608             :     required bool isEnabled,
     609             :     required VoidCallback onLongPress,
     610             :     required Color effectiveBackgroundColor,
     611             :     required EdgeInsetsGeometry effectiveMargin,
     612             :     double? effectiveWidth,
     613             :     double? effectiveHeight,
     614             :   }) {
     615             :     final Color effectiveIconColor =
     616           1 :         _iconColor(context, isEnabled) ?? Theme.of(context).iconTheme.color!;
     617             : 
     618             :     // **KEY MODIFICATION:**
     619             :     // When the button is disabled, set onLongPress to null to prevent the timer from running.
     620             :     final VoidCallback? effectiveOnLongPress = isEnabled ? onLongPress : null;
     621             : 
     622           1 :     Widget button = CustomActionButton.longPress(
     623             :       margin: effectiveMargin,
     624             :       width: effectiveWidth,
     625             :       height: effectiveHeight,
     626             :       backgroundColor: effectiveBackgroundColor,
     627           2 :       shape: widget.buttonShape ??
     628           1 :           RoundedRectangleBorder(
     629           3 :             borderRadius: BorderRadius.circular(widget.borderRadius ?? 10.0),
     630             :           ),
     631           2 :       elevation: widget.elevation ?? 0,
     632           2 :       padding: widget.buttonPadding,
     633             :       onPressed: onPressed,
     634             :       onLongPress: effectiveOnLongPress,
     635             :       // Pass null if disabled
     636           1 :       child: IconTheme(
     637           1 :         data: IconThemeData(color: effectiveIconColor),
     638             :         child: icon,
     639             :       ),
     640             :     );
     641             : 
     642             :     if (effectiveWidth == null || effectiveHeight == null) {
     643           1 :       return Expanded(child: button);
     644             :     } else {
     645           0 :       return SizedBox(
     646             :         width: effectiveWidth,
     647             :         height: effectiveHeight,
     648             :         child: button,
     649             :       );
     650             :     }
     651             :   }
     652             : 
     653             :   /// Builds the quantity display widget.
     654             :   ///
     655             :   /// Displays the current quantity in the center between the increment and decrement buttons.
     656             :   ///
     657             :   /// - [context]: The build context.
     658             :   /// - [padding]: The padding around the quantity text.
     659           1 :   Widget _buildQuantityDisplay(
     660             :     BuildContext context,
     661             :     EdgeInsetsGeometry padding,
     662             :   ) {
     663           1 :     return Expanded(
     664             :       flex: 0,
     665           1 :       child: Container(
     666             :         alignment: Alignment.center,
     667             :         padding: padding,
     668           1 :         child: Text(
     669           2 :           _currentQuantity.toString(),
     670           2 :           style: widget.quantityTextStyle ??
     671           3 :               Theme.of(context).textTheme.titleLarge,
     672             :         ),
     673             :       ),
     674             :     );
     675             :   }
     676             : 
     677             :   /// Determines the icon color based on the button's enabled state.
     678             :   ///
     679             :   /// - [context]: The build context.
     680             :   /// - [isEnabled]: Whether the button is active.
     681             :   ///
     682             :   /// Returns a color with reduced opacity if the button is disabled.
     683           1 :   Color? _iconColor(BuildContext context, bool isEnabled) {
     684             :     final Color defaultColor =
     685           5 :         widget.iconColor ?? Theme.of(context).iconTheme.color!;
     686           1 :     return isEnabled ? defaultColor : defaultColor.withOpacity(0.2);
     687             :   }
     688             : }

Generated by: LCOV version 1.14