Skip to content

Commit

Permalink
Merge pull request #235 from woltapp/introduce-side-sheet
Browse files Browse the repository at this point in the history
Feature: Refactor modal types and introduce side sheet
  • Loading branch information
ulusoyca authored Jun 29, 2024
2 parents 13daf14 + 716933c commit 3845796
Show file tree
Hide file tree
Showing 24 changed files with 1,024 additions and 534 deletions.
4 changes: 2 additions & 2 deletions coffee_maker/lib/home/online/store_online_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ class _StoreOnlineContentState extends State<StoreOnlineContent> {
WoltModalType _modalTypeBuilder(BuildContext context) {
switch (context.screenSize) {
case WoltScreenSize.small:
return WoltModalType.bottomSheet;
return WoltModalType.bottomSheet();
case WoltScreenSize.large:
return WoltModalType.dialog;
return WoltModalType.dialog();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ void _onCoffeeOrderSelectedInReadyState(
WoltModalType _modalTypeBuilder(BuildContext context) {
switch (context.screenSize) {
case WoltScreenSize.small:
return WoltModalType.bottomSheet;
return WoltModalType.bottomSheet();
case WoltScreenSize.large:
return WoltModalType.dialog;
return WoltModalType.dialog();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ class _WoltSelectionListState<T> extends State<WoltSelectionList<T>> {

return ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (_, index) {
final listItemData = _itemTileDataGroup.group.elementAtOrNull(index);
Expand Down
18 changes: 10 additions & 8 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ Pagination involves a sequence of screens the user navigates sequentially. We ch
topBarShadowColor: _darkThemeShadowColor,
modalBarrierColor: Colors.white12,
sabGradientColor: _darkSabGradientColor,
dialogShape: BeveledRectangleBorder(),
bottomSheetShape: BeveledRectangleBorder(),
mainContentScrollPhysics: ClampingScrollPhysics(),
),
],
Expand Down Expand Up @@ -188,19 +186,23 @@ Pagination involves a sequence of screens the user navigates sequentially. We ch
modalTypeBuilder: (context) {
final size = MediaQuery.sizeOf(context).width;
if (size < _pageBreakpoint) {
return WoltModalType.bottomSheet;
return _isLightTheme
? const WoltBottomSheetType()
: const WoltBottomSheetType().copyWith(
shapeBorder: const BeveledRectangleBorder(),
);
} else {
return WoltModalType.dialog;
return _isLightTheme
? const WoltDialogType()
: const WoltDialogType().copyWith(
shapeBorder: const BeveledRectangleBorder(),
);
}
},
onModalDismissedWithBarrierTap: () {
debugPrint('Closed modal sheet with barrier tap');
Navigator.of(context).pop();
},
maxDialogWidth: 560,
minDialogWidth: 400,
minPageHeight: 0.0,
maxPageHeight: 0.9,
);
},
child: const SizedBox(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class WoltModalSheetMainContent extends StatelessWidget {
? navBarHeight
: 0.0;
final isNonScrollingPage = page is NonScrollingWoltModalSheetPage;
final shouldFillRemaining = woltModalType.forceMaxHeight ||
(page.forceMaxHeight && !isNonScrollingPage);
final scrollView = CustomScrollView(
shrinkWrap: true,
physics: themeData?.mainContentScrollPhysics ??
Expand Down Expand Up @@ -87,7 +89,7 @@ class WoltModalSheetMainContent extends StatelessWidget {
...page.mainContentSlivers!
else
...page.mainContentSliversBuilder!(context),
if (page.forceMaxHeight && !isNonScrollingPage)
if (shouldFillRemaining)
const SliverFillRemaining(
hasScrollBody: false,
child: SizedBox.shrink(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class NonScrollingWoltModalSheetPage extends SliverWoltModalSheetPage {
super.topBar,
super.topBarTitle,
super.navBarHeight,
super.useSafeArea,
}) : super(
isTopBarLayerAlwaysVisible: hasTopBarLayer,
mainContentSliversBuilder: (_) => [
Expand Down
9 changes: 9 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 @@ -229,6 +229,10 @@ class SliverWoltModalSheetPage {
/// The default value is set in [WoltModalSheetDefaultThemeData.resizeToAvoidBottomInset].
final bool? resizeToAvoidBottomInset;

/// A boolean that determines whether the modal should avoid system UI intrusions such as the
/// notch and system gesture areas.
final bool? useSafeArea;

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

Expand Down Expand Up @@ -338,6 +345,7 @@ class SliverWoltModalSheetPage {
Widget? leadingNavBarWidget,
Widget? trailingNavBarWidget,
bool? resizeToAvoidBottomInset,
bool? useSafeArea,
Widget? child,
}) {
return SliverWoltModalSheetPage(
Expand Down Expand Up @@ -366,6 +374,7 @@ class SliverWoltModalSheetPage {
trailingNavBarWidget: trailingNavBarWidget ?? this.trailingNavBarWidget,
resizeToAvoidBottomInset:
resizeToAvoidBottomInset ?? this.resizeToAvoidBottomInset,
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 @@ -64,6 +64,7 @@ class WoltModalSheetPage extends SliverWoltModalSheetPage {
super.leadingNavBarWidget,
super.trailingNavBarWidget,
super.topBar,
super.useSafeArea,
}) : super(
mainContentSliversBuilder: (context) => [
SliverToBoxAdapter(child: child),
Expand Down
29 changes: 29 additions & 0 deletions lib/src/modal_type/wolt_alert_dialog_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class WoltAlertDialogType extends WoltDialogType {
const WoltAlertDialogType();

@override
BoxConstraints layoutModal(Size availableSize) {
const padding = 32.0;
final availableWidth = availableSize.width;
double width = availableWidth > 523.0 ? 312.0 : availableWidth - padding;

if (availableWidth > 523.0) {
width = 312.0; // optimal width for larger screens
} else if (availableWidth > 240.0) {
width = 240.0; // standard width for moderate screens
} else {
width = availableWidth - padding; // adjust for very small screens
}
final height = availableSize.height * 0.8;

return BoxConstraints(
minWidth: width,
maxWidth: width,
minHeight: height,
maxHeight: height,
);
}
}
170 changes: 170 additions & 0 deletions lib/src/modal_type/wolt_bottom_sheet_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

/// A customizable bottom sheet modal that extends [WoltModalType].
///
/// This class implements a bottom sheet modal with predefined styles and behaviors.
/// It is designed to be draggable, allowing users to dismiss it by swiping down. This behavior
/// can be overridden at the page level and modal level.
class WoltBottomSheetType extends WoltModalType {
/// Creates a [WoltBottomSheetType] with optional customizations.
const WoltBottomSheetType({
ShapeBorder shapeBorder = _defaultShapeBorder,
bool? isDragEnabled = true,
bool forceMaxHeight = false,
Duration transitionDuration = _defaultEnterDuration,
Duration reverseTransitionDuration = _defaultExitDuration,
}) : super(
shapeBorder: shapeBorder,
isDragToDismissEnabled: isDragEnabled,
forceMaxHeight: forceMaxHeight,
transitionDuration: transitionDuration,
reverseTransitionDuration: reverseTransitionDuration,
);

static const Duration _defaultEnterDuration = Duration(milliseconds: 250);
static const Duration _defaultExitDuration = Duration(milliseconds: 200);
static const ShapeBorder _defaultShapeBorder = RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16.0)),
);

/// Provides an accessibility label for the bottom sheet, enhancing accessibility for users
/// with visual impairments.
///
/// This label helps screen readers describe the function and type of the bottom sheet.
///
/// [context] is used to access localized strings.
///
/// Returns a localized description of the bottom sheet, typically "bottom sheet".
@override
String routeLabel(BuildContext context) =>
MaterialLocalizations.of(context).bottomSheetLabel;

/// Specifies the size constraints for the modal based on the available space.
///
/// This implementation ensures the modal uses the full width of the screen and limits its maximum
/// height to 90% of the screen height, while maintaining a minimum height of 40% of the screen
/// height.
///
/// [availableSize] defines the total available space for the modal.
///
/// Returns [BoxConstraints] that dictate the allowable size of the bottom sheet.
@override
BoxConstraints layoutModal(Size availableSize) {
return BoxConstraints(
minWidth: availableSize.width,
maxWidth: availableSize.width,
minHeight: availableSize.height * 0.4,
maxHeight: availableSize.height * 0.9,
);
}

/// Calculates the starting position for the modal within its parent container.
///
/// The modal is positioned to start at the bottom of the available space.
///
/// [availableSize] is the size of the parent container or screen.
/// [modalContentSize] is the actual size of the modal, which might be less than the maximum
/// depending on content.
///
/// Returns an [Offset] representing the starting position of the modal.
@override
Offset positionModal(Size availableSize, Size modalContentSize, _) {
return Offset(0, max(0.0, availableSize.height - modalContentSize.height));
}

/// Enhances the visual presentation of the modal by filling the safe area constraints with the
/// tinted background color.
///
/// This is useful for modals that must respect the device's screen edges, such as the notch or
/// bottom navigation areas. If [useSafeArea] is `true`, the modal content is wrapped with safe
/// area constraints filled with the tinted background color.
///
/// [context] is the build context.
/// [child] is the content widget of the modal.
/// [useSafeArea] determines whether safe area constraints.
///
/// Returns a widget that includes the modal content, optionally wrapped with safe area constraints.
@override
Widget decoratePageContent(
BuildContext context,
Widget child,
bool useSafeArea,
) {
return useSafeArea
? SafeArea(
top: false,
child: MediaQuery.removePadding(
context: context,
removeTop: true,
child: child,
),
)
: child;
}

@override
Widget decorateModal(
BuildContext context,
Widget modal,
bool useSafeArea,
) =>
useSafeArea
? SafeArea(
top: false,
bottom: false,
child: modal,
)
: modal;

/// Defines the animation for the modal's appearance with a vertical slide transition.
///
/// This method customizes how the modal enters the screen, emphasizing a smooth and natural motion from the bottom to the top.
///
/// [context] is the build context.
/// [animation] is the primary animation controller for the modal's appearance.
/// [secondaryAnimation] coordinates with the transitions of other routes.
/// [child] is the content widget to be animated.
///
/// Returns a `SlideTransition` widget that manages the modal's entrance animation.
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SlideTransition(
position: animation.drive(
Tween(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).chain(CurveTween(curve: Curves.ease)),
),
child: child,
);
}

/// Provides a way to create a new `WoltBottomSheetType` instance with modified properties.
///
/// Useful for tweaking the appearance or behavior of the bottom sheet without creating a
/// completely new class.
WoltBottomSheetType copyWith({
ShapeBorder? shapeBorder,
bool? isDragToDismissEnabled,
bool? forceMaxHeight,
Duration? transitionDuration,
Duration? reverseTransitionDuration,
}) {
return WoltBottomSheetType(
shapeBorder: shapeBorder ?? this.shapeBorder,
isDragEnabled: isDragToDismissEnabled ?? this.isDragToDismissEnabled,
forceMaxHeight: forceMaxHeight ?? this.forceMaxHeight,
transitionDuration: transitionDuration ?? this.transitionDuration,
reverseTransitionDuration:
reverseTransitionDuration ?? this.reverseTransitionDuration,
);
}
}
Loading

0 comments on commit 3845796

Please sign in to comment.