import 'package:flutter/material.dart';

class DraggableBottomSheet extends StatefulWidget {

  // alignment	        Alignment	          Alignment of the sheet
  // backgroundWidget	  Widget	            Widget above which draggable sheet will be placed.
  // barrierColor	      Color	              Color of the modal barrier. Default Colors.black54
  // barrierDismissible	bool	              Collapse bottom sheet on tap. If false, sheet will act as persistent sheet. Default true.
  // collapsed	        bool	              Whether the sheet is collapsed initially. Default true.
  // curve	            Curve	              Sheet expansion animation curve. Default Curves.linear
  // duration	          Duration	          Duration for sheet expansion animation. Default Duration(milliseconds: 0)
  // expandedWidget	    Widget	            Widget to show on expended sheet
  // expansionExtent	  double	            Increment on [minExtent] to change from [previewWidget] to [expandedWidget]
  // maxExtent	        double	            Maximum extent for sheet expansion
  // minExtent	        double	            Minimum extent for the sheet
  // onDragging	        Function	          Callback function when sheet is being dragged
  // previewWidget	    Widget	            Widget to show on collapsed sheet
  // useSafeArea	      bool	              Should dialog only display in 'safe' areas of the screen. Default true
  // useWidgetPreview   bool                true -> preview
  final Alignment alignment;

  final Widget backgroundWidget;

  final Color barrierColor;

  final bool barrierDismissible;

  final bool collapsed;

  final Curve curve;

  final Duration duration;

  final Widget expandedWidget;

  final double expansionExtent;

  final double maxExtent;

  final double minExtent;

  final Function(double) onDragging;

  final Widget? previewWidget;

  final bool useSafeArea;

  final bool useWidgetPreview;

  const DraggableBottomSheet({
    Key? key,
    this.previewWidget,
    required this.backgroundWidget,
    required this.expandedWidget,
    required this.onDragging,
    this.minExtent = 50.0,
    this.collapsed = true,
    this.useSafeArea = true,
    this.curve = Curves.linear,
    this.expansionExtent = 10.0,
    this.barrierDismissible = true,
    this.maxExtent = double.infinity,
    this.barrierColor = Colors.black54,
    this.alignment = Alignment.bottomCenter,
    required this.useWidgetPreview,
    this.duration = const Duration(milliseconds: 0),
  })  : assert(minExtent > 0.0),
        assert(expansionExtent > 0.0),
        assert(minExtent + expansionExtent < maxExtent),
        super(key: key);

  @override
  DraggableBottomSheetState createState() => DraggableBottomSheetState();
}

class DraggableBottomSheetState extends State<DraggableBottomSheet> {
  double _currentExtent = 0.0;

  @override
  void initState() {
    super.initState();
    _currentExtent = widget.collapsed ? widget.minExtent : widget.maxExtent;
  }

  @override
  Widget build(BuildContext context) {
    return widget.useSafeArea ? SafeArea(child: _body()) : _body();
  }

  Widget _body() {
    return Stack(
      children: [
        // background widget
        widget.backgroundWidget,
        // barrier
        if (_currentExtent.roundToDouble() > widget.minExtent + 0.1)
          Positioned.fill(child: _barrier()),
        // sheet
        Align(alignment: widget.alignment, child: _sheet()),
      ],
    );
  }

  Widget _barrier() {
    return IgnorePointer(
      ignoring: !widget.barrierDismissible,
      child: GestureDetector(
        onTap: widget.barrierDismissible
            ? () => setState(() => _currentExtent = widget.minExtent)
            : null,
        child: Container(color: widget.barrierColor),
      ),
    );
  }

  Widget _sheet() {
    return GestureDetector(
      onVerticalDragUpdate: _onVerticalDragUpdate,
      onHorizontalDragUpdate: _onHorizontalDragUpdate,
      child: AnimatedContainer(
        curve: widget.curve,
        duration: widget.duration,
        width: _axis() == Axis.horizontal ? _currentExtent : null,
        height: _axis() == Axis.horizontal ? null : _currentExtent,
        child: widget.useWidgetPreview?_currentExtent >= widget.minExtent + widget.expansionExtent
            ? widget.expandedWidget
            : widget.previewWidget
        :widget.expandedWidget,
      ),
    );
  }

  Axis _axis() {
    if (widget.alignment == Alignment.topLeft ||
        widget.alignment == Alignment.topRight ||
        widget.alignment == Alignment.topCenter ||
        widget.alignment == Alignment.bottomLeft ||
        widget.alignment == Alignment.bottomRight ||
        widget.alignment == Alignment.bottomCenter) {
      return Axis.vertical;
    }

    return Axis.horizontal;
  }

  /// callback function when sheet is dragged horizontally
  void _onHorizontalDragUpdate(DragUpdateDetails details) {
    if (_axis() == Axis.vertical) return;
    final newExtent = (_currentExtent + details.delta.dx).roundToDouble();
    if (newExtent >= widget.minExtent && newExtent <= widget.maxExtent) {
      setState(() => _currentExtent = newExtent);
      widget.onDragging(_currentExtent);
    }
  }

  void _onVerticalDragUpdate(DragUpdateDetails details) {
    if (_axis() == Axis.horizontal) return;
    final newExtent = (_currentExtent - details.delta.dy).roundToDouble();
    if (newExtent >= widget.minExtent && newExtent <= widget.maxExtent) {
      setState(() => _currentExtent = newExtent);
      widget.onDragging(_currentExtent);
    }
  }
}