LCOV - code coverage report
Current view: top level - buttons - custom_action_button.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 130 139 93.5 %
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             : /// Types of buttons available in [CustomActionButton].
       6             : enum ButtonType { elevated, flat, minimal, longPress }
       7             : 
       8             : /// A customizable button widget that can be configured as elevated, flat,
       9             : /// minimal, or long-press button types. Provides a flexible API to adjust
      10             : /// styles, colors, shapes, and behaviors.
      11             : ///
      12             : /// The [CustomActionButton] supports different visual styles through the
      13             : /// [ButtonType] enum and offers factory constructors for convenience.
      14             : ///
      15             : /// Example usage:
      16             : /// ```dart
      17             : /// CustomActionButton.elevated(
      18             : ///   onPressed: () {},
      19             : ///   child: Text('Elevated Button'),
      20             : /// );
      21             : /// ```
      22             : class CustomActionButton extends StatefulWidget {
      23             :   /// The callback that is called when the button is tapped.
      24             :   final VoidCallback? onPressed;
      25             : 
      26             :   /// The callback that is called when the button is long-pressed.
      27             :   /// Only used when [buttonType] is [ButtonType.longPress].
      28             :   final VoidCallback? onLongPress;
      29             : 
      30             :   /// The child widget to display inside the button.
      31             :   final Widget child;
      32             : 
      33             :   /// The type of button to display.
      34             :   final ButtonType? buttonType;
      35             : 
      36             :   /// The background color of the button.
      37             :   final Color? backgroundColor;
      38             : 
      39             :   /// The foreground color (text/icon color) of the button.
      40             :   final Color? foregroundColor;
      41             : 
      42             :   /// The shadow color of the button.
      43             :   final Color? shadowColor;
      44             : 
      45             :   /// The splash color of the button when tapped.
      46             :   final Color? splashColor;
      47             : 
      48             :   /// The background color of the button when it is disabled.
      49             :   final Color? disabledBackgroundColor;
      50             : 
      51             :   /// The border color of the button when it is disabled.
      52             :   final Color? disabledBorderColor;
      53             : 
      54             :   /// The border color of the button.
      55             :   final Color? borderColor;
      56             : 
      57             :   /// The elevation of the button.
      58             :   final double? elevation;
      59             : 
      60             :   /// The border radius of the button.
      61             :   final double? borderRadius;
      62             : 
      63             :   /// The width of the button.
      64             :   final double? width;
      65             : 
      66             :   /// The height of the button.
      67             :   final double? height;
      68             : 
      69             :   /// The shape of the button's material.
      70             :   final OutlinedBorder? shape;
      71             : 
      72             :   /// The amount of space to surround the child inside the button.
      73             :   final EdgeInsetsGeometry? padding;
      74             : 
      75             :   /// The external margin around the button.
      76             :   final EdgeInsetsGeometry? margin;
      77             : 
      78             :   /// The splash factory to define interaction effects.
      79             :   final InteractiveInkFeatureFactory? splashFactory;
      80             : 
      81             :   /// Creates a [CustomActionButton] with the given parameters.
      82           4 :   const CustomActionButton({
      83             :     super.key,
      84             :     required this.child,
      85             :     this.buttonType,
      86             :     this.onPressed,
      87             :     this.onLongPress,
      88             :     this.backgroundColor,
      89             :     this.foregroundColor,
      90             :     this.shadowColor,
      91             :     this.splashColor,
      92             :     this.disabledBackgroundColor,
      93             :     this.disabledBorderColor,
      94             :     this.borderColor,
      95             :     this.elevation,
      96             :     this.borderRadius,
      97             :     this.width,
      98             :     this.height,
      99             :     this.shape,
     100             :     this.padding,
     101             :     this.margin,
     102             :     this.splashFactory,
     103             :   });
     104             : 
     105             :   /// Creates an elevated button.
     106             :   ///
     107             :   /// The [onPressed] and [child] parameters are required.
     108           1 :   factory CustomActionButton.elevated({
     109             :     required VoidCallback? onPressed,
     110             :     required Widget child,
     111             :     Color? backgroundColor,
     112             :     Color? foregroundColor,
     113             :     Color? shadowColor,
     114             :     Color? splashColor,
     115             :     Color? disabledBackgroundColor,
     116             :     Color? disabledForegroundColor,
     117             :     Color? borderColor,
     118             :     double elevation = 2.0,
     119             :     double borderRadius = 8.0,
     120             :     BorderSide? side,
     121             :     OutlinedBorder? shape,
     122             :     double? width,
     123             :     double? height,
     124             :     EdgeInsetsGeometry? padding,
     125             :     EdgeInsetsGeometry? margin,
     126             :     InteractiveInkFeatureFactory? splashFactory,
     127             :   }) {
     128           1 :     return CustomActionButton(
     129             :       buttonType: ButtonType.elevated,
     130             :       onPressed: onPressed,
     131             :       foregroundColor: foregroundColor,
     132             :       backgroundColor: backgroundColor,
     133             :       shadowColor: shadowColor,
     134             :       splashColor: splashColor,
     135             :       disabledBackgroundColor: disabledBackgroundColor,
     136             :       disabledBorderColor: disabledForegroundColor,
     137             :       borderColor: borderColor,
     138             :       elevation: elevation,
     139             :       borderRadius: borderRadius,
     140             :       shape: shape,
     141             :       width: width,
     142             :       height: height,
     143             :       padding: padding,
     144             :       margin: margin,
     145             :       splashFactory: splashFactory,
     146             :       child: child,
     147             :     );
     148             :   }
     149             : 
     150             :   /// Creates a flat button.
     151             :   ///
     152             :   /// The [onPressed] and [child] parameters are required.
     153           2 :   factory CustomActionButton.flat({
     154             :     required VoidCallback? onPressed,
     155             :     required Widget child,
     156             :     Color? backgroundColor,
     157             :     Color? foregroundColor,
     158             :     Color? splashColor,
     159             :     Color? disabledBackgroundColor,
     160             :     Color? disabledForegroundColor,
     161             :     Color? borderColor,
     162             :     double borderRadius = 8.0,
     163             :     BorderSide? side,
     164             :     OutlinedBorder? shape,
     165             :     double? width,
     166             :     double? height,
     167             :     EdgeInsetsGeometry? padding,
     168             :     EdgeInsetsGeometry? margin,
     169             :     InteractiveInkFeatureFactory? splashFactory,
     170             :   }) {
     171           2 :     return CustomActionButton(
     172             :       buttonType: ButtonType.flat,
     173             :       onPressed: onPressed,
     174             :       foregroundColor: foregroundColor,
     175             :       backgroundColor: backgroundColor,
     176             :       splashColor: splashColor,
     177             :       disabledBackgroundColor: disabledBackgroundColor,
     178             :       disabledBorderColor: disabledForegroundColor,
     179             :       borderColor: borderColor,
     180             :       borderRadius: borderRadius,
     181             :       shape: shape,
     182             :       width: width,
     183             :       height: height,
     184             :       padding: padding,
     185             :       margin: margin,
     186             :       splashFactory: splashFactory,
     187             :       child: child,
     188             :     );
     189             :   }
     190             : 
     191             :   /// Creates a minimal button.
     192             :   ///
     193             :   /// The [onPressed] and [child] parameters are required.
     194           1 :   factory CustomActionButton.minimal({
     195             :     required VoidCallback? onPressed,
     196             :     required Widget child,
     197             :     Color? foregroundColor,
     198             :     Color? disabledForegroundColor,
     199             :     Color? borderColor,
     200             :     double? width,
     201             :     double? height,
     202             :     OutlinedBorder? shape,
     203             :     EdgeInsetsGeometry? padding,
     204             :     EdgeInsetsGeometry? margin,
     205             :   }) {
     206           1 :     return CustomActionButton(
     207             :       buttonType: ButtonType.minimal,
     208             :       onPressed: onPressed,
     209             :       foregroundColor: foregroundColor,
     210             :       disabledBorderColor: disabledForegroundColor,
     211             :       borderColor: borderColor,
     212             :       width: width,
     213             :       height: height,
     214             :       shape: shape,
     215             :       padding: padding,
     216             :       margin: margin,
     217             :       child: child,
     218             :     );
     219             :   }
     220             : 
     221             :   /// Creates a long-press button.
     222             :   ///
     223             :   /// The [onPressed], [onLongPress], and [child] parameters are required.
     224           2 :   factory CustomActionButton.longPress({
     225             :     required VoidCallback? onPressed,
     226             :     required VoidCallback? onLongPress,
     227             :     required Widget child,
     228             :     Color? backgroundColor,
     229             :     Color? foregroundColor,
     230             :     Color? shadowColor,
     231             :     Color? splashColor,
     232             :     Color? disabledBackgroundColor,
     233             :     Color? disabledForegroundColor,
     234             :     Color? borderColor,
     235             :     double elevation = 2.0,
     236             :     double borderRadius = 8.0,
     237             :     BorderSide? side,
     238             :     OutlinedBorder? shape,
     239             :     double? width,
     240             :     double? height,
     241             :     EdgeInsetsGeometry? padding,
     242             :     EdgeInsetsGeometry? margin,
     243             :     InteractiveInkFeatureFactory? splashFactory,
     244             :   }) {
     245           2 :     return CustomActionButton(
     246             :       buttonType: ButtonType.longPress,
     247             :       onPressed: onPressed,
     248             :       onLongPress: onLongPress,
     249             :       foregroundColor: foregroundColor,
     250             :       backgroundColor: backgroundColor,
     251             :       shadowColor: shadowColor,
     252             :       splashColor: splashColor,
     253             :       disabledBackgroundColor: disabledBackgroundColor,
     254             :       disabledBorderColor: disabledForegroundColor,
     255             :       borderColor: borderColor,
     256             :       elevation: elevation,
     257             :       borderRadius: borderRadius,
     258             :       shape: shape,
     259             :       width: width,
     260             :       height: height,
     261             :       padding: padding,
     262             :       margin: margin,
     263             :       splashFactory: splashFactory,
     264             :       child: child,
     265             :     );
     266             :   }
     267             : 
     268           4 :   @override
     269           4 :   State<CustomActionButton> createState() => _CustomActionButtonState();
     270             : }
     271             : 
     272             : class _CustomActionButtonState extends State<CustomActionButton> {
     273             :   Timer? _longPressTimer;
     274             : 
     275             :   /// Handles the long-press action by repeatedly invoking [widget.onLongPress]
     276             :   /// at a fixed interval.
     277           1 :   void _handleLongPress() {
     278           2 :     if (widget.onLongPress != null) {
     279           2 :       _longPressTimer = Timer.periodic(
     280             :         const Duration(milliseconds: 100),
     281           1 :         (timer) {
     282           3 :           widget.onLongPress?.call();
     283             :         },
     284             :       );
     285             :     }
     286             :   }
     287             : 
     288             :   /// Cancels the ongoing long-press action.
     289           1 :   void _cancelLongPress() {
     290           2 :     _longPressTimer?.cancel();
     291             :   }
     292             : 
     293           4 :   @override
     294             :   Widget build(BuildContext context) {
     295          17 :     if (widget.onPressed == null && widget.buttonType != ButtonType.longPress) {
     296           2 :       return _buildDisabledButton(context);
     297             :     }
     298             : 
     299           8 :     switch (widget.buttonType) {
     300           4 :       case ButtonType.minimal:
     301           1 :         return _buildMinimalButton(context);
     302           4 :       case ButtonType.longPress:
     303           2 :         return _buildLongPressButton(context);
     304           3 :       case ButtonType.elevated:
     305           1 :         return _buildElevatedButton(context);
     306             :       case ButtonType.flat:
     307             :       default:
     308           3 :         return _buildFlatButton(context);
     309             :     }
     310             :   }
     311             : 
     312             :   /// Builds a disabled button when [onPressed] is null.
     313           2 :   Widget _buildDisabledButton(BuildContext context) {
     314           2 :     final ButtonStyle buttonStyle = ElevatedButton.styleFrom(
     315             :       overlayColor: Colors.transparent,
     316             :       surfaceTintColor: Colors.transparent,
     317           4 :       foregroundColor: widget.foregroundColor ?? Colors.transparent,
     318           4 :       backgroundColor: widget.disabledBackgroundColor ??
     319           4 :           widget.backgroundColor ??
     320           2 :           Theme.of(context).primaryColor,
     321           4 :       shadowColor: widget.shadowColor ?? Colors.black,
     322           4 :       padding: widget.padding ??
     323             :           const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
     324           4 :       shape: widget.shape ??
     325           2 :           RoundedRectangleBorder(
     326           6 :             borderRadius: BorderRadius.circular(widget.borderRadius ?? 8.0),
     327           8 :             side: (widget.disabledBorderColor ?? widget.borderColor) != null
     328           0 :                 ? BorderSide(
     329           0 :                     color: widget.disabledBorderColor ??
     330           0 :                         widget.borderColor ??
     331             :                         Colors.transparent,
     332             :                     width: 1)
     333             :                 : BorderSide.none,
     334             :           ),
     335           4 :       elevation: widget.elevation,
     336             :     );
     337             : 
     338           2 :     return Container(
     339           4 :       margin: widget.margin,
     340           4 :       width: widget.width,
     341           4 :       height: widget.height,
     342           2 :       child: AbsorbPointer(
     343             :         absorbing: true,
     344           2 :         child: ElevatedButton(
     345             :           style: buttonStyle,
     346           0 :           onPressed: () {},
     347           4 :           child: widget.child,
     348             :         ),
     349             :       ),
     350             :     );
     351             :   }
     352             : 
     353             :   /// Builds an elevated button style.
     354           1 :   Widget _buildElevatedButton(BuildContext context) {
     355           1 :     final ButtonStyle buttonStyle = ElevatedButton.styleFrom(
     356           2 :       foregroundColor: widget.foregroundColor ?? Colors.white,
     357           2 :       backgroundColor: widget.backgroundColor ?? Theme.of(context).primaryColor,
     358           2 :       shadowColor: widget.shadowColor,
     359           2 :       padding: widget.padding ??
     360             :           const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
     361           2 :       shape: widget.shape ??
     362           1 :           RoundedRectangleBorder(
     363           3 :             borderRadius: BorderRadius.circular(widget.borderRadius ?? 8.0),
     364           2 :             side: widget.borderColor != null
     365           0 :                 ? BorderSide(color: widget.borderColor!, width: 1)
     366             :                 : BorderSide.none,
     367             :           ),
     368           2 :       elevation: widget.elevation ?? 2.0,
     369           2 :       overlayColor: widget.splashColor ?? Colors.transparent,
     370           2 :       splashFactory: widget.splashFactory,
     371             :     );
     372             : 
     373           1 :     return Container(
     374           2 :       margin: widget.margin,
     375           2 :       width: widget.width,
     376           2 :       height: widget.height,
     377           1 :       child: ElevatedButton(
     378             :         style: buttonStyle,
     379           2 :         onPressed: widget.onPressed,
     380           2 :         child: widget.child,
     381             :       ),
     382             :     );
     383             :   }
     384             : 
     385             :   /// Builds a flat button style.
     386           3 :   Widget _buildFlatButton(BuildContext context) {
     387           3 :     final ButtonStyle buttonStyle = TextButton.styleFrom(
     388           6 :       foregroundColor: widget.foregroundColor ?? Colors.white,
     389           6 :       backgroundColor: widget.backgroundColor ?? Theme.of(context).primaryColor,
     390           9 :       overlayColor: widget.splashColor ?? Colors.grey.withOpacity(0.2),
     391           6 :       padding: widget.padding ??
     392             :           const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
     393           6 :       shape: widget.shape ??
     394           3 :           RoundedRectangleBorder(
     395           9 :             borderRadius: BorderRadius.circular(widget.borderRadius ?? 8.0),
     396           6 :             side: widget.borderColor != null
     397           0 :                 ? BorderSide(color: widget.borderColor!, width: 1)
     398             :                 : BorderSide.none,
     399             :           ),
     400           6 :       splashFactory: widget.splashFactory ?? InkRipple.splashFactory,
     401             :     );
     402             : 
     403           3 :     return Container(
     404           6 :       margin: widget.margin,
     405           6 :       width: widget.width,
     406           6 :       height: widget.height,
     407           3 :       child: TextButton(
     408             :         style: buttonStyle,
     409           6 :         onPressed: widget.onPressed,
     410           6 :         child: widget.child,
     411             :       ),
     412             :     );
     413             :   }
     414             : 
     415             :   /// Builds a minimal button style.
     416           1 :   Widget _buildMinimalButton(BuildContext context) {
     417           1 :     final ButtonStyle buttonStyle = TextButton.styleFrom(
     418           2 :       foregroundColor: widget.foregroundColor ?? Colors.black,
     419           2 :       padding: widget.padding ??
     420             :           const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
     421           2 :       shape: widget.shape ?? const RoundedRectangleBorder(),
     422             :       backgroundColor: Colors.transparent,
     423           2 :       side: widget.borderColor != null
     424           0 :           ? BorderSide(color: widget.borderColor!, width: 1)
     425             :           : BorderSide.none,
     426           1 :     ).copyWith(
     427           1 :       overlayColor: WidgetStateProperty.all(Colors.transparent),
     428             :       splashFactory: NoSplash.splashFactory,
     429             :     );
     430             : 
     431           1 :     return Container(
     432           2 :       margin: widget.margin,
     433           2 :       width: widget.width,
     434           2 :       height: widget.height,
     435           1 :       child: TextButton(
     436             :         style: buttonStyle,
     437           2 :         onPressed: widget.onPressed,
     438           2 :         child: widget.child,
     439             :       ),
     440             :     );
     441             :   }
     442             : 
     443             :   /// Builds a button that supports long-press actions.
     444           2 :   Widget _buildLongPressButton(BuildContext context) {
     445           2 :     final ButtonStyle buttonStyle = ElevatedButton.styleFrom(
     446           4 :       foregroundColor: widget.foregroundColor ?? Colors.white,
     447           6 :       backgroundColor: widget.backgroundColor ?? Theme.of(context).primaryColor,
     448           4 :       shadowColor: widget.shadowColor,
     449           4 :       padding: widget.padding ??
     450             :           const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
     451           4 :       shape: widget.shape ??
     452           1 :           RoundedRectangleBorder(
     453           3 :             borderRadius: BorderRadius.circular(widget.borderRadius ?? 8.0),
     454           2 :             side: widget.borderColor != null
     455           0 :                 ? BorderSide(color: widget.borderColor!, width: 1)
     456             :                 : BorderSide.none,
     457             :           ),
     458           4 :       elevation: widget.elevation ?? 2.0,
     459           2 :     ).copyWith(
     460           4 :       overlayColor: widget.splashColor != null
     461           0 :           ? WidgetStateProperty.all(widget.splashColor)
     462             :           : null,
     463           4 :       splashFactory: widget.splashFactory,
     464             :     );
     465             : 
     466           2 :     return Container(
     467           4 :       margin: widget.margin,
     468           4 :       width: widget.width,
     469           4 :       height: widget.height,
     470           2 :       child: GestureDetector(
     471           4 :         onTap: widget.onPressed,
     472           2 :         onLongPressStart: (_) => _handleLongPress(),
     473           2 :         onLongPressEnd: (_) => _cancelLongPress(),
     474           2 :         child: ElevatedButton(
     475             :           style: buttonStyle,
     476           4 :           onPressed: widget.onPressed,
     477           4 :           child: widget.child,
     478             :         ),
     479             :       ),
     480             :     );
     481             :   }
     482             : 
     483           4 :   @override
     484             :   void dispose() {
     485           5 :     _longPressTimer?.cancel();
     486           4 :     super.dispose();
     487             :   }
     488             : }

Generated by: LCOV version 1.14