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

          Line data    Source code
       1             : import 'package:flutter/cupertino.dart';
       2             : import 'package:flutter/material.dart';
       3             : 
       4             : import '../buttons/custom_action_button.dart';
       5             : import '../buttons/list_tile_button.dart';
       6             : 
       7             : /// A class responsible for displaying customized modal bottom sheets.
       8             : ///
       9             : /// The [OpenCustomSheet] class provides a flexible and reusable way to present
      10             : /// modal bottom sheets with various customization options, such as padding,
      11             : /// colors, shapes, and default action buttons. It supports both scrollable and
      12             : /// non-scrollable sheets, and includes factory constructors for common sheet
      13             : /// configurations like confirmation dialogs and scrollable content areas.
      14             : class OpenCustomSheet {
      15             :   /// Determines whether the sheet can be dismissed by tapping outside of it.
      16             :   ///
      17             :   /// Defaults to `true`.
      18             :   final bool barrierDismissible;
      19             : 
      20             :   /// The semantic label for the barrier.
      21             :   ///
      22             :   /// Useful for accessibility purposes. If not provided, defaults to null.
      23             :   final String? barrierLabel;
      24             : 
      25             :   /// The color of the modal barrier that darkens everything below the sheet.
      26             :   ///
      27             :   /// If not specified, it defaults to `Colors.black54`.
      28             :   final Color? barrierColor;
      29             : 
      30             :   /// Callback invoked when the sheet is closed.
      31             :   ///
      32             :   /// Receives the result passed to `Navigator.pop`.
      33             :   final Function(dynamic)? onClose;
      34             : 
      35             :   /// The main content of the sheet.
      36             :   ///
      37             :   /// A function that returns a widget. It can optionally receive a [ScrollController]
      38             :   /// if the sheet is scrollable.
      39             :   final Widget Function({ScrollController? scrollController}) body;
      40             : 
      41             :   /// Determines if the sheet is scrollable.
      42             :   ///
      43             :   /// If `true`, the sheet uses a [DraggableScrollableSheet] to allow scrolling.
      44             :   /// Defaults to `false`.
      45             :   final bool scrollable;
      46             : 
      47             :   /// Whether the sheet should expand to fill the available space.
      48             :   ///
      49             :   /// Defaults to `true`.
      50             :   final bool expand;
      51             : 
      52             :   /// The initial size of the sheet as a fraction of the screen height.
      53             :   ///
      54             :   /// Only applicable if [scrollable] is `true`. Defaults to `0.5`.
      55             :   final double initialChildSize;
      56             : 
      57             :   /// The minimum size the sheet can be dragged down to, as a fraction of the screen height.
      58             :   ///
      59             :   /// Only applicable if [scrollable] is `true`. Defaults to `0.25`.
      60             :   final double minChildSize;
      61             : 
      62             :   /// The maximum size the sheet can be dragged up to, as a fraction of the screen height.
      63             :   ///
      64             :   /// Only applicable if [scrollable] is `true`. Defaults to `1.0`.
      65             :   final double maxChildSize;
      66             : 
      67             :   /// The background color of the sheet.
      68             :   ///
      69             :   /// If not specified, it defaults to the theme's card color.
      70             :   final Color? backgroundColor;
      71             : 
      72             :   /// The color of the handle (the small bar at the top of the sheet) used for dragging.
      73             :   ///
      74             :   /// If not specified, it defaults to `CupertinoColors.inactiveGray`.
      75             :   final Color? handleColor;
      76             : 
      77             :   /// The shape of the sheet's border.
      78             :   ///
      79             :   /// Allows for customizing the sheet's outline and corners. If not specified,
      80             :   /// a default rounded rectangle is used.
      81             :   final ShapeBorder? sheetShape;
      82             : 
      83             :   /// The padding inside the sheet.
      84             :   ///
      85             :   /// Applies padding around the entire content of the sheet. If not specified,
      86             :   /// it defaults to `EdgeInsets.only(bottom: 16.0)`.
      87             :   final EdgeInsetsGeometry? sheetPadding;
      88             : 
      89             :   /// The background color of the first action button.
      90             :   ///
      91             :   /// Typically used for the cancellation or negative action. If not specified,
      92             :   /// it defaults to `Colors.red`.
      93             :   final Color? firstButtonColor;
      94             : 
      95             :   /// The background color of the second action button.
      96             :   ///
      97             :   /// Typically used for the confirmation or positive action. If not specified,
      98             :   /// it defaults to `Colors.green`.
      99             :   final Color? secondButtonColor;
     100             : 
     101             :   /// The text color of the first action button.
     102             :   ///
     103             :   /// If not specified, it defaults to `Colors.white`.
     104             :   final Color? firstButtonTextColor;
     105             : 
     106             :   /// The text color of the second action button.
     107             :   ///
     108             :   /// If not specified, it defaults to `Colors.white`.
     109             :   final Color? secondButtonTextColor;
     110             : 
     111             :   /// The text label for the confirmation button.
     112             :   ///
     113             :   /// If not specified, it defaults to `'Confirm'`.
     114             :   final String? confirmButtonText;
     115             : 
     116             :   /// The text label for the cancellation button.
     117             :   ///
     118             :   /// If not specified, it defaults to `'Close'`.
     119             :   final String? cancelButtonText;
     120             : 
     121             :   /// The padding around the action buttons.
     122             :   ///
     123             :   /// If not specified, it defaults to `EdgeInsets.symmetric(vertical: 10, horizontal: 8)`.
     124             :   final EdgeInsetsGeometry? padding;
     125             : 
     126             :   /// The spacing between the action buttons.
     127             :   ///
     128             :   /// If not specified, it defaults to `8.0`.
     129             :   final double? buttonSpacing;
     130             : 
     131             :   /// Determines whether to show default action buttons (Confirm and Cancel).
     132             :   ///
     133             :   /// If set to `true`, the sheet will include these buttons at the bottom.
     134             :   /// Defaults to `false`.
     135             :   final bool showDefaultButtons;
     136             : 
     137             :   /// Creates an instance of [OpenCustomSheet] with the specified properties.
     138             :   ///
     139             :   /// The [body] parameter is required and must not be null. Other parameters are optional
     140             :   /// and have default values if not specified.
     141           1 :   const OpenCustomSheet({
     142             :     this.barrierDismissible = true,
     143             :     this.barrierColor,
     144             :     this.barrierLabel,
     145             :     this.onClose,
     146             :     required this.body,
     147             :     this.scrollable = false,
     148             :     this.expand = true,
     149             :     this.initialChildSize = 0.5,
     150             :     this.minChildSize = 0.25,
     151             :     this.maxChildSize = 1.0,
     152             :     this.backgroundColor,
     153             :     this.handleColor,
     154             :     this.sheetShape,
     155             :     this.sheetPadding,
     156             :     this.firstButtonColor,
     157             :     this.secondButtonColor,
     158             :     this.firstButtonTextColor,
     159             :     this.secondButtonTextColor,
     160             :     this.confirmButtonText,
     161             :     this.cancelButtonText,
     162             :     this.padding,
     163             :     this.buttonSpacing,
     164             :     this.showDefaultButtons = false, // Changed default to false
     165             :   });
     166             : 
     167             :   /// Factory constructor to create a confirmation sheet with default action buttons.
     168             :   ///
     169             :   /// The confirmation sheet includes Confirm and Cancel buttons at the bottom.
     170             :   ///
     171             :   /// **Example usage:**
     172             :   /// ```dart
     173             :   /// OpenCustomSheet.openConfirmSheet(
     174             :   ///   context,
     175             :   ///   body: ({scrollController}) => Text('Are you sure you want to proceed?'),
     176             :   ///   onClose: (result) {
     177             :   ///     if (result == true) {
     178             :   ///       // Handle confirmation
     179             :   ///     } else {
     180             :   ///       // Handle cancellation
     181             :   ///     }
     182             :   ///   },
     183             :   /// );
     184             :   /// ```
     185           1 :   factory OpenCustomSheet.openConfirmSheet(
     186             :     BuildContext context, {
     187             :     required Widget body,
     188             :     Function(dynamic)? onClose,
     189             :     Color? backgroundColor,
     190             :     Color? handleColor,
     191             :     bool barrierDismissible = true,
     192             :     Color? firstButtonColor,
     193             :     Color? secondButtonColor,
     194             :     Color? firstButtonTextColor,
     195             :     Color? secondButtonTextColor,
     196             :     String? confirmButtonText,
     197             :     String? cancelButtonText,
     198             :     EdgeInsetsGeometry? padding,
     199             :     double? buttonSpacing,
     200             :   }) {
     201           1 :     return OpenCustomSheet(
     202             :       barrierDismissible: barrierDismissible,
     203           1 :       barrierColor: Colors.black.withOpacity(0.5),
     204             :       onClose: onClose,
     205             :       backgroundColor: backgroundColor,
     206             :       handleColor: handleColor,
     207             :       padding: padding,
     208             :       buttonSpacing: buttonSpacing,
     209             :       firstButtonColor: firstButtonColor,
     210             :       secondButtonColor: secondButtonColor,
     211             :       firstButtonTextColor: firstButtonTextColor,
     212             :       secondButtonTextColor: secondButtonTextColor,
     213             :       confirmButtonText: confirmButtonText,
     214             :       cancelButtonText: cancelButtonText,
     215             :       showDefaultButtons: true,
     216             :       // Enable default buttons for confirm sheet
     217           1 :       body: ({scrollController}) => body,
     218             :     );
     219             :   }
     220             : 
     221             :   /// Factory constructor to create a scrollable sheet without default action buttons.
     222             :   ///
     223             :   /// This is useful for displaying large amounts of content that require scrolling.
     224             :   ///
     225             :   /// **Example usage:**
     226             :   /// ```dart
     227             :   /// OpenCustomSheet.scrollableSheet(
     228             :   ///   context,
     229             :   ///   body: ({scrollController}) => ListView.builder(
     230             :   ///     controller: scrollController,
     231             :   ///     itemCount: 50,
     232             :   ///     itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
     233             :   ///   ),
     234             :   /// );
     235             :   /// ```
     236           1 :   factory OpenCustomSheet.scrollableSheet(
     237             :     BuildContext context, {
     238             :     required Widget Function({ScrollController? scrollController}) body,
     239             :     Function(dynamic)? onClose,
     240             :     bool expand = true,
     241             :     double initialChildSize = 0.5,
     242             :     double minChildSize = 0.25,
     243             :     double maxChildSize = 1.0,
     244             :     Color? barrierColor,
     245             :     Color? backgroundColor,
     246             :     Color? handleColor,
     247             :     bool barrierDismissible = true,
     248             :     ShapeBorder? sheetShape,
     249             :     EdgeInsetsGeometry? sheetPadding,
     250             :   }) {
     251           1 :     return OpenCustomSheet(
     252             :       scrollable: true,
     253             :       expand: expand,
     254             :       initialChildSize: initialChildSize,
     255             :       minChildSize: minChildSize,
     256             :       maxChildSize: maxChildSize,
     257             :       onClose: onClose,
     258             :       barrierColor: barrierColor,
     259             :       barrierDismissible: barrierDismissible,
     260             :       backgroundColor: backgroundColor,
     261             :       handleColor: handleColor,
     262             :       sheetShape: sheetShape,
     263             :       sheetPadding: sheetPadding,
     264             :       showDefaultButtons: false,
     265             :       // Disable default buttons for scrollable sheet
     266             :       body: body,
     267             :     );
     268             :   }
     269             : 
     270             :   /// Displays the custom sheet using [showModalBottomSheet].
     271             :   ///
     272             :   /// This method configures the sheet based on the properties provided during
     273             :   /// instantiation, such as scrollability, padding, colors, and action buttons.
     274             :   ///
     275             :   /// After the sheet is closed, the [onClose] callback is invoked with the result.
     276           1 :   void show(BuildContext context) {
     277           1 :     showModalBottomSheet(
     278             :       backgroundColor: Colors.transparent,
     279           1 :       isDismissible: barrierDismissible,
     280           1 :       barrierColor: barrierColor,
     281           1 :       barrierLabel: barrierLabel,
     282             :       isScrollControlled: true,
     283             :       useSafeArea: true,
     284             :       context: context,
     285           1 :       builder: (context) {
     286           1 :         final mediaQuery = MediaQuery.of(context);
     287           3 :         final maxHeight = mediaQuery.size.height * 0.9;
     288           1 :         if (scrollable) {
     289           1 :           return DraggableScrollableSheet(
     290           1 :             expand: expand,
     291           1 :             initialChildSize: initialChildSize,
     292           1 :             minChildSize: minChildSize,
     293           1 :             maxChildSize: maxChildSize,
     294           1 :             builder: (context, scrollController) {
     295           1 :               return Container(
     296           1 :                 decoration: BoxDecoration(
     297             :                   borderRadius:
     298             :                       const BorderRadius.vertical(top: Radius.circular(10)),
     299           1 :                   color: backgroundColor ?? Theme.of(context).cardColor,
     300             :                 ),
     301           1 :                 padding: sheetPadding ?? const EdgeInsets.only(bottom: 16.0),
     302           1 :                 child: Column(
     303           1 :                   children: [
     304           2 :                     if (handleColor != Colors.transparent)
     305           2 :                       _buildHandle(handleColor),
     306           1 :                     Expanded(
     307           2 :                       child: body(scrollController: scrollController),
     308             :                     ),
     309             :                   ],
     310             :                 ),
     311             :               );
     312             :             },
     313             :           );
     314             :         } else {
     315             :           // For non-scrollable sheet, enable scrolling only if necessary
     316           1 :           return Container(
     317           1 :             constraints: BoxConstraints(
     318             :               maxHeight: maxHeight,
     319             :             ),
     320           1 :             decoration: BoxDecoration(
     321             :               borderRadius:
     322             :                   const BorderRadius.vertical(top: Radius.circular(10)),
     323           3 :               color: backgroundColor ?? Theme.of(context).cardColor,
     324             :             ),
     325           1 :             padding: sheetPadding ?? const EdgeInsets.only(bottom: 16.0),
     326           1 :             child: Column(
     327             :               mainAxisSize: MainAxisSize.min,
     328           1 :               children: [
     329           2 :                 if (handleColor != Colors.transparent)
     330           2 :                   _buildHandle(handleColor),
     331           1 :                 Flexible(
     332           1 :                   child: SingleChildScrollView(
     333           1 :                     child: Padding(
     334             :                       padding: const EdgeInsets.symmetric(
     335             :                           horizontal: 30, vertical: 20),
     336           2 :                       child: body(scrollController: null),
     337             :                     ),
     338             :                   ),
     339             :                 ),
     340           1 :                 if (showDefaultButtons)
     341           1 :                   _buildButtons(
     342             :                     context,
     343           1 :                     firstButtonColor,
     344           1 :                     secondButtonColor,
     345           1 :                     firstButtonTextColor,
     346           1 :                     secondButtonTextColor,
     347           1 :                     buttonSpacing,
     348           1 :                     confirmButtonText,
     349           1 :                     cancelButtonText,
     350             :                   ),
     351             :               ],
     352             :             ),
     353             :           );
     354             :         }
     355             :       },
     356           2 :     ).then((value) {
     357           1 :       if (onClose != null) {
     358           2 :         onClose!(value);
     359             :       }
     360             :     });
     361             :   }
     362             : 
     363             :   /// Builds the confirmation and cancellation buttons for the sheet.
     364             :   ///
     365             :   /// These buttons are displayed at the bottom of the sheet and handle user actions
     366             :   /// like confirming or cancelling an operation.
     367             :   ///
     368             :   /// - [context]: The build context.
     369             :   /// - [firstButtonColor]: The background color for the first button (e.g., Cancel).
     370             :   /// - [secondButtonColor]: The background color for the second button (e.g., Confirm).
     371             :   /// - [firstButtonTextColor]: The text color for the first button.
     372             :   /// - [secondButtonTextColor]: The text color for the second button.
     373             :   /// - [buttonSpacing]: The spacing between the buttons.
     374             :   /// - [confirmButtonText]: The label for the confirmation button.
     375             :   /// - [cancelButtonText]: The label for the cancellation button.
     376             :   ///
     377             :   /// **Example usage:**
     378             :   /// ```dart
     379             :   /// _buildButtons(
     380             :   ///   context,
     381             :   ///   Colors.red,
     382             :   ///   Colors.green,
     383             :   ///   Colors.white,
     384             :   ///   Colors.white,
     385             :   ///   10.0,
     386             :   ///   'Yes',
     387             :   ///   'No',
     388             :   /// );
     389             :   /// ```
     390           1 :   static Widget _buildButtons(
     391             :     BuildContext context,
     392             :     Color? firstButtonColor,
     393             :     Color? secondButtonColor,
     394             :     Color? firstButtonTextColor,
     395             :     Color? secondButtonTextColor,
     396             :     double? buttonSpacing,
     397             :     String? confirmButtonText,
     398             :     String? cancelButtonText,
     399             :   ) {
     400           1 :     return Padding(
     401             :       padding: const EdgeInsets.only(top: 16.0),
     402           1 :       child: DoubleListTileButtons(
     403             :         padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
     404             :         space: buttonSpacing ?? 8,
     405           1 :         firstButton: CustomActionButton.flat(
     406             :           margin: EdgeInsets.zero,
     407             :           width: double.infinity,
     408           2 :           onPressed: () => Navigator.pop(context, false),
     409             :           backgroundColor: firstButtonColor ?? Colors.red,
     410           1 :           child: Text(
     411             :             cancelButtonText ?? 'Close',
     412           4 :             style: Theme.of(context).textTheme.labelLarge!.copyWith(
     413             :                   color: firstButtonTextColor ?? Colors.white,
     414             :                 ),
     415             :           ),
     416             :         ),
     417           1 :         secondButton: CustomActionButton.flat(
     418           2 :           onPressed: () => Navigator.pop(context, true),
     419             :           margin: EdgeInsets.zero,
     420             :           width: double.infinity,
     421             :           backgroundColor: secondButtonColor ?? Colors.green,
     422           1 :           child: Text(
     423             :             confirmButtonText ?? 'Confirm',
     424           4 :             style: Theme.of(context).textTheme.labelLarge!.copyWith(
     425             :                   color: secondButtonTextColor ?? Colors.white,
     426             :                 ),
     427             :           ),
     428             :         ),
     429             :       ),
     430             :     );
     431             :   }
     432             : 
     433             :   /// Builds the drag handle for the sheet.
     434             :   ///
     435             :   /// The handle provides a visual indicator that the sheet can be dragged.
     436             :   ///
     437             :   /// - [handleColor]: The color of the handle. If not specified, it defaults to
     438             :   ///   `CupertinoColors.inactiveGray`.
     439             :   ///
     440             :   /// **Example usage:**
     441             :   /// ```dart
     442             :   /// _buildHandle(Colors.blue);
     443             :   /// ```
     444           1 :   static Widget _buildHandle(Color? handleColor) {
     445           1 :     return Center(
     446           1 :       child: Container(
     447             :         width: 150,
     448             :         height: 5,
     449           1 :         decoration: BoxDecoration(
     450             :           color: handleColor ?? CupertinoColors.inactiveGray,
     451           1 :           borderRadius: BorderRadius.circular(20),
     452             :         ),
     453             :         margin: const EdgeInsets.all(10),
     454             :       ),
     455             :     );
     456             :   }
     457             : }

Generated by: LCOV version 1.14