Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable drag to dismiss modal if the scroll position is at top edge #283

Merged
merged 4 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,104 @@ class WoltModalSheetMainContent extends StatelessWidget {
final SliverWoltModalSheetPage page;
final WoltModalType woltModalType;
final WoltModalSheetScrollAnimationStyle scrollAnimationStyle;
final WoltModalSheetRoute route;
final VoidCallback? onModalDismissedWithDrag;
final GlobalKey modalContentKey;

const WoltModalSheetMainContent({
required this.scrollController,
required this.pageTitleKey,
required this.page,
required this.woltModalType,
required this.scrollAnimationStyle,
required this.route,
this.onModalDismissedWithDrag,
required this.modalContentKey,
Key? key,
}) : super(key: key);
RenderBox get _renderBox =>
modalContentKey.currentContext!.findRenderObject()! as RenderBox;

double get _childHeight => _renderBox.size.height;
bool get _shouldDragContent =>
AcarFurkan marked this conversation as resolved.
Show resolved Hide resolved
(page.contentDraggable ?? false) && woltModalType is WoltBottomSheetType;

AnimationController get _animationController => route.animationController!;

double get _minFlingVelocity => woltModalType.minFlingVelocity;

bool get _isDismissUnderway =>
_animationController.status == AnimationStatus.reverse;

bool get _isDismissed => _animationController.isDismissed;

WoltModalDismissDirection? get _dismissDirection =>
woltModalType.dismissDirection;

void _handleVerticalDragUpdate(
DragUpdateDetails details, BuildContext context) {
if (_isDismissUnderway || _isDismissed) {
return;
}

final deltaDiff = details.primaryDelta! / _childHeight;
_animationController.value -= deltaDiff;

if (_dismissDirection == WoltModalDismissDirection.down) {
_animationController.value -= deltaDiff;
} else if (_dismissDirection == WoltModalDismissDirection.up) {
_animationController.value += deltaDiff;
}
}

void _handleVerticalDragEnd(BuildContext context, DragEndDetails details) {
if (_isDismissUnderway || _isDismissed) {
return;
}
bool isClosing = false;
bool isDraggingUpward = details.velocity.pixelsPerSecond.dy < 0;
bool isDraggingDownward = details.velocity.pixelsPerSecond.dy > 0;
final isDismissUp = _dismissDirection?.isUp ?? false;
final isDismissDown = _dismissDirection?.isDown ?? false;
final double flingVelocity =
details.velocity.pixelsPerSecond.dy / _childHeight;

if (isDraggingUpward && isDismissUp) {
if (details.velocity.pixelsPerSecond.dy.abs() > _minFlingVelocity) {
_animationController.fling(velocity: flingVelocity);
if (flingVelocity > 0.0) {
isClosing = true;
}
}
}

if (isDraggingDownward && isDismissDown) {
if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
_animationController.fling(velocity: flingVelocity * -1.0);
if (flingVelocity < 0.0) {
isClosing = true;
}
}
}

if (_animationController.value < woltModalType.closeProgressThreshold) {
if (_animationController.value > 0.0) {
_animationController.fling(velocity: -1.0);
}
isClosing = true;
} else {
_animationController.forward();
}

if (isClosing && route.isCurrent) {
final onModalDismissedWithDrag = this.onModalDismissedWithDrag;
if (onModalDismissedWithDrag != null) {
onModalDismissedWithDrag();
} else {
Navigator.pop(context);
}
}
}

@override
Widget build(BuildContext context) {
Expand All @@ -48,53 +137,77 @@ class WoltModalSheetMainContent extends StatelessWidget {
final isNonScrollingPage = page is NonScrollingWoltModalSheetPage;
final shouldFillRemaining = woltModalType.forceMaxHeight ||
(page.forceMaxHeight && !isNonScrollingPage);
final scrollView = CustomScrollView(
shrinkWrap: true,
physics: themeData?.mainContentScrollPhysics ??
defaultThemeData.mainContentScrollPhysics,
controller: scrollController,
slivers: [
if (!isNonScrollingPage)
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == 0) {
final heroImage = page.heroImage;
return heroImage != null
? WoltModalSheetHeroImage(
topBarHeight: topBarHeight,
heroImage: heroImage,
heroImageHeight: heroImageHeight,
scrollAnimationStyle: scrollAnimationStyle,
)
// If top bar layer is always visible, the padding is explicitly added to the
// scroll view since top bar will not be integrated to scroll view at all.
// Otherwise, we implicitly create a spacing as a part of the scroll view.
: SizedBox(
height:
isTopBarLayerAlwaysVisible ? 0 : topBarHeight);
} else {
final pageTitle = page.pageTitle;
return KeyedSubtree(
key: pageTitleKey,
child: pageTitle ?? const SizedBox.shrink(),
);

final scrollView = NotificationListener(
onNotification: _shouldDragContent
? (ScrollNotification notification) {
if (notification is OverscrollNotification) {
if (notification.dragDetails != null) {
_handleVerticalDragUpdate(notification.dragDetails!, context);
}
}
if (notification is ScrollEndNotification) {
if (notification.dragDetails != null) {
_handleVerticalDragEnd(context, notification.dragDetails!);
}
},
childCount: 2,
}
if (notification is ScrollStartNotification) {
print('start');
}

return true;
}
: null,
child: CustomScrollView(
shrinkWrap: true,
physics: _shouldDragContent
? const ClampingScrollPhysics()
: (themeData?.mainContentScrollPhysics ??
defaultThemeData.mainContentScrollPhysics),
controller: scrollController,
slivers: [
if (!isNonScrollingPage)
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == 0) {
final heroImage = page.heroImage;
return heroImage != null
? WoltModalSheetHeroImage(
topBarHeight: topBarHeight,
heroImage: heroImage,
heroImageHeight: heroImageHeight,
scrollAnimationStyle: scrollAnimationStyle,
)
// If top bar layer is always visible, the padding is explicitly added to the
// scroll view since top bar will not be integrated to scroll view at all.
// Otherwise, we implicitly create a spacing as a part of the scroll view.
: SizedBox(
height:
isTopBarLayerAlwaysVisible ? 0 : topBarHeight);
} else {
final pageTitle = page.pageTitle;
return KeyedSubtree(
key: pageTitleKey,
child: pageTitle ?? const SizedBox.shrink(),
);
}
},
childCount: 2,
),
),
if (page.mainContentSliversBuilder == null)
// ignore: deprecated_member_use_from_same_package
...page.mainContentSlivers!
else
...page.mainContentSliversBuilder!(context),
if (shouldFillRemaining)
const SliverFillRemaining(
hasScrollBody: false,
child: SizedBox.shrink(),
),
),
if (page.mainContentSliversBuilder == null)
// ignore: deprecated_member_use_from_same_package
...page.mainContentSlivers!
else
...page.mainContentSliversBuilder!(context),
if (shouldFillRemaining)
const SliverFillRemaining(
hasScrollBody: false,
child: SizedBox.shrink(),
),
],
],
),
);
return Padding(
// The scroll view should be padded by the height of the top bar layer if it's always
Expand Down
9 changes: 9 additions & 0 deletions lib/src/content/wolt_modal_sheet_animated_switcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ class WoltModalSheetAnimatedSwitcher extends StatefulWidget {
final WoltModalType woltModalType;
final double sheetWidth;
final bool showDragHandle;
final WoltModalSheetRoute route;
final VoidCallback? onModalDismissedWithDrag;
final GlobalKey modalContentKey;

const WoltModalSheetAnimatedSwitcher({
required this.pages,
required this.pageIndex,
required this.woltModalType,
required this.sheetWidth,
required this.showDragHandle,
required this.route,
this.onModalDismissedWithDrag,
required this.modalContentKey,
Key? key,
}) : assert(pageIndex >= 0 && pageIndex < pages.length),
super(key: key);
Expand Down Expand Up @@ -486,6 +492,9 @@ class _WoltModalSheetAnimatedSwitcherState
scrollController: scrollController,
page: _page,
woltModalType: widget.woltModalType,
route: widget.route,
onModalDismissedWithDrag: widget.onModalDismissedWithDrag,
modalContentKey: widget.modalContentKey,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class NonScrollingWoltModalSheetPage extends SliverWoltModalSheetPage {
super.topBarTitle,
super.navBarHeight,
super.useSafeArea,
super.contentDraggable,
}) : super(
isTopBarLayerAlwaysVisible: hasTopBarLayer,
mainContentSliversBuilder: (_) => [
Expand Down
11 changes: 11 additions & 0 deletions lib/src/modal_page/sliver_wolt_modal_sheet_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ class SliverWoltModalSheetPage {
/// notch and system gesture areas.
final bool? useSafeArea;

/// Controls the draggability of the bottom sheet content. This setting overrides the value provided
/// via [WoltModalSheet.show] specifically for this page when the modal is displayed as a bottom sheet.
final bool? contentDraggable;
ulusoyca marked this conversation as resolved.
Show resolved Hide resolved



/// Creates a page to be built within [WoltScrollableModalSheet].
const SliverWoltModalSheetPage({
this.mainContentSlivers,
Expand All @@ -258,6 +264,7 @@ class SliverWoltModalSheetPage {
this.isTopBarLayerAlwaysVisible,
this.resizeToAvoidBottomInset,
this.useSafeArea,
this.contentDraggable,
}) : assert(!(topBar != null && hasTopBarLayer == false),
"When topBar is provided, hasTopBarLayer must not be false"),
assert(
Expand Down Expand Up @@ -287,6 +294,7 @@ class SliverWoltModalSheetPage {
Widget? trailingNavBarWidget,
bool? resizeToAvoidBottomInset,
bool? useSafeArea,
bool? contentDraggable,
Widget? child,
}) {
return SliverWoltModalSheetPage(
Expand Down Expand Up @@ -315,6 +323,7 @@ class SliverWoltModalSheetPage {
resizeToAvoidBottomInset:
resizeToAvoidBottomInset ?? this.resizeToAvoidBottomInset,
useSafeArea: useSafeArea ?? this.useSafeArea,
contentDraggable: contentDraggable ?? this.contentDraggable,
);
}

Expand Down Expand Up @@ -346,6 +355,7 @@ class SliverWoltModalSheetPage {
Widget? trailingNavBarWidget,
bool? resizeToAvoidBottomInset,
bool? useSafeArea,
bool? contentDraggable,
Widget? child,
}) {
return SliverWoltModalSheetPage(
Expand Down Expand Up @@ -374,6 +384,7 @@ class SliverWoltModalSheetPage {
trailingNavBarWidget: trailingNavBarWidget ?? this.trailingNavBarWidget,
resizeToAvoidBottomInset:
resizeToAvoidBottomInset ?? this.resizeToAvoidBottomInset,
contentDraggable: contentDraggable ?? this.contentDraggable,
useSafeArea: useSafeArea ?? this.useSafeArea,
);
}
Expand Down
1 change: 1 addition & 0 deletions lib/src/modal_page/wolt_modal_sheet_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class WoltModalSheetPage extends SliverWoltModalSheetPage {
super.trailingNavBarWidget,
super.topBar,
super.useSafeArea,
super.contentDraggable,
}) : super(
mainContentSliversBuilder: (context) => [
SliverToBoxAdapter(child: child),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ class WoltModalSheetContentGestureDetector extends StatelessWidget {
if (_isDismissUnderway || _isDismissed) {
return;
}

final deltaDiff = details.primaryDelta! / _childHeight;
if (_dismissDirection == WoltModalDismissDirection.down) {
_animationController.value -= deltaDiff;
Expand Down
6 changes: 5 additions & 1 deletion lib/src/wolt_modal_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class WoltModalSheet<T> extends StatefulWidget {
/// A boolean that determines whether the modal should avoid system UI intrusions such as the
/// notch and system gesture areas.
final bool? useSafeArea;
static const ParametricCurve<double> animationCurve = decelerateEasing;
AcarFurkan marked this conversation as resolved.
Show resolved Hide resolved
static const ParametricCurve<double> animationCurve = Easing.legacyDecelerate;

@override
State<WoltModalSheet> createState() => WoltModalSheetState();
Expand Down Expand Up @@ -381,6 +381,10 @@ class WoltModalSheetState extends State<WoltModalSheet> {
pages: pages,
sheetWidth: constraints.maxWidth,
showDragHandle: showDragHandle,
route: widget.route,
onModalDismissedWithDrag:
widget.onModalDismissedWithDrag,
modalContentKey: _childKey,
),
useSafeArea,
);
Expand Down
Loading