diff --git a/lib/components/bottom_sheet/gf_bottom_sheet.dart b/lib/components/bottom_sheet/gf_bottom_sheet.dart index ac4c97b9..03fe7466 100644 --- a/lib/components/bottom_sheet/gf_bottom_sheet.dart +++ b/lib/components/bottom_sheet/gf_bottom_sheet.dart @@ -1,21 +1,25 @@ +import 'dart:async'; import 'package:flutter/material.dart'; class GFBottomSheet extends StatefulWidget { GFBottomSheet({ Key key, - @required this.stickyHeader, @required this.contentBody, + this.stickyHeader, this.stickyFooter, this.controller, this.minContentHeight = 0, this.maxContentHeight = 300, this.elevation = 0.0, - this.stickyFooterHeight, + this.stickyFooterHeight = 0.0, + this.stickyHeaderHeight = 0.0, + this.animationDuration = 300, + this.enableExpandableContent = false, }) : assert(elevation >= 0.0), assert(minContentHeight >= 0.0), super(key: key) { controller.height = minContentHeight; - controller.smoothness = 500; + controller.animationDuration = animationDuration; } /// [minContentHeight] controls the minimum height of the content body. @@ -31,35 +35,49 @@ class GFBottomSheet extends StatefulWidget { final Widget stickyHeader; /// [contentBody] is the body of GFBottomSheet. - /// User can interact by tapping the [contentBody] + /// User can interact by swiping or tapping the [contentBody] final Widget contentBody; /// [stickyFooter] is the footer of GFBottomSheet. /// User can interact by swiping or tapping the [stickyFooter] final Widget stickyFooter; - /// [stickyFooterHeight] defines the height of GFBottokSheet footer. + /// [stickyFooterHeight] defines the height of GFBottomSheet's [stickyFooter]. final double stickyFooterHeight; + /// [stickyHeaderHeight] defines the height of GFBottomSheet's [stickyHeader]. + final double stickyHeaderHeight; + /// [elevation] controls shadow below the GFBottomSheet material. - /// Must be greater or equalto 0. Default value is 0. + /// Must be greater or equal to 0. Default value is 0. final double elevation; + ///[enableExpandableContent] allows [contentBody] to expand. + /// Default value is false. + final bool enableExpandableContent; + /// [controller] used to control GFBottomSheet behavior like hide/show final GFBottomSheetController controller; + /// Defines the drag animation duration of the GFBottomSheet + /// Default value is 300. + final int animationDuration; + @override _GFBottomSheetState createState() => _GFBottomSheetState(); } class _GFBottomSheetState extends State with TickerProviderStateMixin { + final StreamController controller = StreamController.broadcast(); bool isDragDirectionUp; bool showBottomSheet = false; Function _controllerListener; + double position; + bool showContent = false; void _onVerticalDragUpdate(data) { - _setSmoothness(); + _setAnimationDuration(); if (((widget.controller.height - data.delta.dy) > widget.minContentHeight) && ((widget.controller.height - data.delta.dy) < @@ -70,7 +88,7 @@ class _GFBottomSheetState extends State } void _onVerticalDragEnd(data) { - _setSmoothness(); + _setAnimationDuration(); if (isDragDirectionUp && widget.controller.value) { _showBottomSheet(); } else if (!isDragDirectionUp && !widget.controller.value) { @@ -89,7 +107,7 @@ class _GFBottomSheetState extends State @override void initState() { super.initState(); - + position = widget.minContentHeight; widget.controller.value = showBottomSheet; _controllerListener = () { widget.controller.value ? _showBottomSheet() : _hideBottomSheet(); @@ -113,28 +131,62 @@ class _GFBottomSheetState extends State onVerticalDragUpdate: _onVerticalDragUpdate, onVerticalDragEnd: _onVerticalDragEnd, onTap: _onTap, - child: widget.stickyHeader, + child: Container( + height: widget.stickyHeaderHeight, + child: widget.stickyHeader, + ), ), - AnimatedBuilder( - animation: widget.controller, - builder: (_, Widget child) => AnimatedContainer( - curve: Curves.easeOut, - duration: Duration(milliseconds: widget.controller.smoothness), - height: widget.controller.height, - child: GestureDetector( - onVerticalDragUpdate: _onVerticalDragUpdate, - onVerticalDragEnd: _onVerticalDragEnd, - onTap: _onTap, - child: widget.contentBody), - ), - ), - widget.stickyFooter != null + !widget.enableExpandableContent ? AnimatedBuilder( animation: widget.controller, builder: (_, Widget child) => AnimatedContainer( curve: Curves.easeOut, - duration: - Duration(milliseconds: widget.controller.smoothness), + duration: Duration( + milliseconds: widget.controller.animationDuration), + height: widget.controller.height, + child: GestureDetector( + onVerticalDragUpdate: _onVerticalDragUpdate, + onVerticalDragEnd: _onVerticalDragEnd, + onTap: _onTap, + child: widget.contentBody), + ), + ) + : showContent + ? StreamBuilder( + stream: controller.stream, + initialData: widget.controller.height, + builder: (context, snapshot) => GestureDetector( + onVerticalDragUpdate: (DragUpdateDetails details) { + if (((widget.controller.height - details.delta.dy) > + widget.minContentHeight) && + ((widget.controller.height - details.delta.dy) < + (MediaQuery.of(context).size.height * 0.8 - + widget.stickyFooterHeight - + widget.stickyHeaderHeight))) { + isDragDirectionUp = details.delta.dy <= 0; + widget.controller.height -= details.delta.dy; + } + controller.add(widget.controller.height); + }, + onVerticalDragEnd: _onVerticalDragEnd, + onTap: _onTap, + behavior: HitTestBehavior.translucent, + child: Container( + height: snapshot.hasData == null + ? widget.minContentHeight + : snapshot.data, + child: widget.contentBody, + )), + ) + : Container(), + widget.stickyFooter == null + ? Container() + : AnimatedBuilder( + animation: widget.controller, + builder: (_, Widget child) => AnimatedContainer( + curve: Curves.easeOut, + duration: Duration( + milliseconds: widget.controller.animationDuration), height: widget.controller.height != widget.minContentHeight ? widget.stickyFooterHeight : 0.0, @@ -145,8 +197,7 @@ class _GFBottomSheetState extends State child: widget.stickyFooter, ), ), - ) - : Container(), + ), ], ); return Material( @@ -156,10 +207,16 @@ class _GFBottomSheetState extends State } void _hideBottomSheet() { + setState(() { + showContent = false; + }); widget.controller.height = widget.minContentHeight; } void _showBottomSheet() { + setState(() { + showContent = true; + }); widget.controller.height = widget.maxContentHeight; } @@ -169,8 +226,8 @@ class _GFBottomSheetState extends State super.dispose(); } - void _setSmoothness() { - widget.controller.smoothness = 500; + void _setAnimationDuration() { + widget.controller.animationDuration = widget.animationDuration; } } @@ -180,8 +237,8 @@ class GFBottomSheetController extends ValueNotifier { /// Defines the height of the GFBottomSheet's contentBody double _height; - /// Defines the drag animation smoothness of the GFBottomSheet - int smoothness; + /// Defines the drag animation duration of the GFBottomSheet + int animationDuration; // ignore: unnecessary_getters_setters set height(double value) => _height = value;