Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 89 additions & 32 deletions lib/components/bottom_sheet/gf_bottom_sheet.dart
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<GFBottomSheet>
with TickerProviderStateMixin {
final StreamController<double> 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) <
Expand All @@ -70,7 +88,7 @@ class _GFBottomSheetState extends State<GFBottomSheet>
}

void _onVerticalDragEnd(data) {
_setSmoothness();
_setAnimationDuration();
if (isDragDirectionUp && widget.controller.value) {
_showBottomSheet();
} else if (!isDragDirectionUp && !widget.controller.value) {
Expand All @@ -89,7 +107,7 @@ class _GFBottomSheetState extends State<GFBottomSheet>
@override
void initState() {
super.initState();

position = widget.minContentHeight;
widget.controller.value = showBottomSheet;
_controllerListener = () {
widget.controller.value ? _showBottomSheet() : _hideBottomSheet();
Expand All @@ -113,28 +131,62 @@ class _GFBottomSheetState extends State<GFBottomSheet>
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,
Expand All @@ -145,8 +197,7 @@ class _GFBottomSheetState extends State<GFBottomSheet>
child: widget.stickyFooter,
),
),
)
: Container(),
),
],
);
return Material(
Expand All @@ -156,10 +207,16 @@ class _GFBottomSheetState extends State<GFBottomSheet>
}

void _hideBottomSheet() {
setState(() {
showContent = false;
});
widget.controller.height = widget.minContentHeight;
}

void _showBottomSheet() {
setState(() {
showContent = true;
});
widget.controller.height = widget.maxContentHeight;
}

Expand All @@ -169,8 +226,8 @@ class _GFBottomSheetState extends State<GFBottomSheet>
super.dispose();
}

void _setSmoothness() {
widget.controller.smoothness = 500;
void _setAnimationDuration() {
widget.controller.animationDuration = widget.animationDuration;
}
}

Expand All @@ -180,8 +237,8 @@ class GFBottomSheetController extends ValueNotifier<bool> {
/// 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;
Expand Down