Orientação gerada a partir de informações públicas. Apenas para referência.
wolt_modal_sheet é um pacote Flutter que oferece uma folha modal multi-página responsiva com animações suaves de movimento para transições entre páginas e conteúdo rolagem em cada página. Projetado para experiências do usuário sem interrupções em várias plataformas, suporta Android, iOS, Web, macOS, Windows e Linux. O pacote permite que desenvolvedores criem folhas inferiores interativas com layouts dinâmicos e navegação fluida entre páginas, ideal para formulários, fluxos de introdução ou exibição de informações detalhadas. Sua estrutura leve se integra facilmente a projetos existentes no Flutter.
Aplicativos com interações de usuário em múltiplos passos
Aplicativos web e móveis que precisam de componentes de interface unificados
Considerações
Requer conhecimento do sistema de animação do Flutter
Pode aumentar ligeiramente o tamanho do pacote
Mais adequado para aplicativos de médio a grande escala
Personalização limitada para comportamentos não em sheet inferior
Não otimizado para conteúdo muito longo e rolagem sem ajuste de desempenho
README original
Snapshot do README original do repositório-fonte vinculado. Consulte o repositório para o conteúdo mais recente.
WoltModalSheet
WoltModalSheet is designed to revolutionize the use of modal sheets in
Flutter apps. Built with Wolt-grade design quality and used extensively
in Wolt products, this UI component offers a visually
appealing and highly customizable modal sheets with multiple pages, motion
for page transitions, and scrollable content within each page.
The example app demonstrates how to display a two-pages modal
sheet that can be customized for dark and light themes
using WoltModalSheetThemeData theme
extension.
@overrideWidgetbuild(BuildContext context) {
SliverWoltModalSheetPagepage1(BuildContext modalSheetContext, TextTheme textTheme) {
returnWoltModalSheetPage(
hasSabGradient: false,
stickyActionBar: Padding(
padding: constEdgeInsets.all(_pagePadding),
child: Column(
children: [
ElevatedButton(
onPressed: Navigator.of(modalSheetContext).pop,
child: constSizedBox(
height: _buttonHeight,
width: double.infinity,
child: Center(child: Text('Cancel')),
),
),
constSizedBox(height: 8),
ElevatedButton(
onPressed: WoltModalSheet.of(modalSheetContext).showNext,
child: constSizedBox(
height: _buttonHeight,
width: double.infinity,
child: Center(child: Text('Next page')),
),
),
],
),
),
topBarTitle: Text('Pagination', style: textTheme.titleSmall),
isTopBarLayerAlwaysVisible: true,
trailingNavBarWidget: IconButton(
padding: constEdgeInsets.all(_pagePadding),
icon: constIcon(Icons.close),
onPressed: Navigator.of(modalSheetContext).pop,
),
child: constPadding(
padding: EdgeInsets.fromLTRB(
_pagePadding,
_pagePadding,
_pagePadding,
_bottomPaddingForButton,
),
child: Text(
'''
Pagination involves a sequence of screens the user navigates sequentially. We chose a lateral motion for these transitions. When proceeding forward, the next screen emerges from the right; moving backward, the screen reverts to its original position. We felt that sliding the next screen entirely from the right could be overly distracting. As a result, we decided to move and fade in the next page using 30% of the modal side.
''',
)),
);
}
SliverWoltModalSheetPagepage2(BuildContext modalSheetContext, TextTheme textTheme) {
returnSliverWoltModalSheetPage(
pageTitle: Padding(
padding: constEdgeInsets.all(_pagePadding),
child: Text(
'Material Colors',
style: textTheme.headlineMedium!.copyWith(fontWeight: FontWeight.bold),
),
),
heroImage: Image(
image: NetworkImage(
'https://raw.githubusercontent.com/woltapp/wolt_modal_sheet/main/example/lib/assets/images/material_colors_hero${_isLightTheme ? '_light' : '_dark'}.png',
),
fit: BoxFit.cover,
),
leadingNavBarWidget: IconButton(
padding: constEdgeInsets.all(_pagePadding),
icon: constIcon(Icons.arrow_back_rounded),
onPressed: WoltModalSheet.of(modalSheetContext).showPrevious,
),
trailingNavBarWidget: IconButton(
padding: constEdgeInsets.all(_pagePadding),
icon: constIcon(Icons.close),
onPressed: Navigator.of(modalSheetContext).pop,
),
mainContentSliversBuilder: (context) => [
SliverGrid(
gridDelegate: constSliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 2.0,
),
delegate: SliverChildBuilderDelegate(
(_, index) => ColorTile(color: materialColorsInGrid[index]),
childCount: materialColorsInGrid.length,
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(_, index) => ColorTile(color: materialColorsInSliverList[index]),
childCount: materialColorsInSliverList.length,
),
),
],
);
}
returnMaterialApp(
themeMode: _isLightTheme ? ThemeMode.light : ThemeMode.dark,
theme: ThemeData.light().copyWith(
extensions: const <ThemeExtension>[
WoltModalSheetThemeData(
heroImageHeight: _heroImageHeight,
topBarShadowColor: _lightThemeShadowColor,
modalBarrierColor: Colors.black54,
mainContentScrollPhysics: ClampingScrollPhysics(),
),
],
),
darkTheme: ThemeData.dark().copyWith(
extensions: const <ThemeExtension>[
WoltModalSheetThemeData(
topBarShadowColor: _darkThemeShadowColor,
modalBarrierColor: Colors.white12,
sabGradientColor: _darkSabGradientColor,
mainContentScrollPhysics: ClampingScrollPhysics(),
),
],
),
home: Scaffold(
body: Builder(
builder: (context) {
returnColumn(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(...),
ElevatedButton(
onPressed: () {
WoltModalSheet.show<void>(
context: context,
pageListBuilder: (modalSheetContext) {
final textTheme = Theme.of(context).textTheme;
return [
page1(modalSheetContext, textTheme),
page2(modalSheetContext, textTheme),
];
},
modalTypeBuilder: (context) {
final size = MediaQuery.sizeOf(context).width;
if (size < _pageBreakpoint) {
returnWoltModalType.bottomSheet();
} else {
returnWoltModalType.dialog();
}
},
onModalDismissedWithBarrierTap: () {
debugPrint('Closed modal sheet with barrier tap');
Navigator.of(context).pop();
},
);
},
child: constSizedBox(
height: _buttonHeight,
width: _buttonWidth,
child: Center(child: Text('Show Modal Sheet')),
),
),
],
);
},
),
),
);
}
The example project above will create the following:
Playground app with imperative navigation
The playground app (source code, web app)
demonstrates how to imperatively show the
modal sheet. The purpose of this module is to play and experiment with various
use cases.
Playground app with declarative navigation
The playground_navigator2 app (source code, web app) has the similar content with
the playground app but the modal sheet is shown using
Navigator 2.0 (Router API) in a declarative way.
Coffee maker app for state management example
The coffee_maker app (source code, web app) demonstrates how to manage the state among the page components with an opinionated use of the Provider state
management library.
The following code snippet demonstrates how to decorate the modal sheet with a change
notifier provider so that the page components can be rebuilt according to the
current state:
The coffee_maker_navigator2 (source code, web app) app demonstrates how to manage app navigation including the modal sheet with Navigator 2.0 by
applying the MVVM pattern with the Provide state management package.
CupertinoApp support
In the package, certain Material widgets rely on retrieving Material localizations information
from the widget tree. However, Material localizations are not inherently available in CupertinoApp,
leading to potential errors. To mitigate this issue, if your application utilizes CupertinoApp
rather than MaterialApp, it is needed to incorporate a default Material localization delegate
into your application configuration.
Traverse through numerous pages within a single sheet.
Scrollable Content
Easily accommodates large content by using scrollable pages.
Responsive Design
The modal sheet is responsive, appearing as a dialog or side sheet on large
screens and as a bottom sheet on small screens, guided by user-specified
conditions.
Custom Modal Types
The package offers four customizable modal types: bottom sheet, dialog, side
sheet, and alert dialog. It also supports creating entirely custom modal types.
Motion
Engage users with dynamic motion for page transitions and scrolling.
Pagination
Scrolling
Imperative and Declarative Navigation
The package showcases examples of both imperative and declarative navigation
patterns to display modal sheet on screen.
Dynamic Pagination
User input can dynamically shape the modal sheet's page list.
State Management Integration
Pages in the Wolt Modal Sheet offer a customizable look. The page components
are provided with an instance of WoltModalSheetPage class. The API provides a
way to manage the state among the page components to be used with popular
libraries such as Bloc and Provider.
Modal Types
The WoltModalSheet package provides four modal types which can be
instantiated as follows:
Bottom sheet: WoltBottomSheetType() or WoltModalType.bottomSheet()
Dialog: WoltDialogType() or WoltModalType.dialog()
Side sheet: WoltSideSheetType() or WoltModalType.sideSheet()
Alert dialog: WoltAlertDialogType() or WoltModalType.alertDialog()
These modal types are designed according to the Wolt design system specs and
can be customized to suit your application's design language by extending
these classes. For example:
Custom modal types can be defined by extending the abstract WoltModalType
class. This approach allows you to create modal types that align with your
design language and application requirements.
To define a custom modal type, you need to override the following methods:
routeLabel: Provides an accessibility label for the modal.
layoutModal: Returns the constraints for the modal layout.
positionModal: Returns the offset for the modal position from the
top-left corner of the route screen.
buildTransitions: Allows defining custom animation effects for the modal's
entry and exit.
Additionally, you can override the following properties:
shapeBorder: Defines the shape of the modal.
dismissDirection: Specifies the direction in which the modal can be
dismissed. The dismiss direction can be set to up, down, startToEnd,
or endToStart.
showDragHandle: Determines whether the modal displays a drag handle on top.
closeProgressThreshold: Sets the threshold for closing the modal.
barrierDismissible: Determines whether the modal can be dismissed by
tapping outside the modal.
enableDrag: Enables or disables dragging the modal.
minFlingVelocity: Sets the minimum fling velocity required for a drag
gesture to be considered a fling.
transitionDuration and reverseTransitionDuration: Specifies the
duration of the modal's entry and exit animations.
forceMaxHeight: Forces the modal content to have a maximum height.
Useful for modal types such as side sheets.
The modal types are designed to be responsive, adapting to different screen
sizes. The modal type's layout, position, and animation can be customized
dynamically based on the available screen size.
The above code snippet produces the following modal types based on the
dynamical screen width:
Decorating modal types, modal, and pages
WoltModalSheet uses the decorator pattern, which is a structural design
pattern allowing dynamic addition of behavior to individual objects. In
Flutter, this is typically achieved by wrapping widgets with other widgets
to enhance or modify their behavior.
Decoration Approaches
Decoration can be achieved at both the modal type level and the modal level.
Modal Type Level Decoration
The modal type level decoration is applied to all modals of the same type.
To decorate at the modal type level, the corresponding WoltModalType class
should be extended and the related methods should be overridden.
Decoration at the modal level is applied when using the modal and can be
applied to all modal types when the modal sheet is visible. This is done
through the pageContentDecorator and modalDecorator.
Example:
WoltModalSheet.show(
context: context,
pageContentDecorator: (widget) => Align(
alignment: Alignment.bottomCenter,
child: ClipRRect(
..., // Your clipRRect properties
child: BackdropFilter(
..., // Your backdrop filter properties
child: widget,
),
),
),
modalDecorator: (child) {
// Wrap the modal with `ChangeNotifierProvider` to manage the state of // the entire pages in the modal.returnChangeNotifierProvider<StoreOnlineViewModel>(
builder: (_, __) => StoreOnlineViewModel(),
child: child,
);
},
pageListBuilder: (context) {
final viewModel = context.read<StoreOnlineViewModel>();
return [
WoltModalSheetPage(
child: FirstPageContent(viewModel.data),
pageTitle: Text('First Page Title'),
// Other properties...
),
WoltModalSheetPage(
child: SecondPageContent(viewModel.data),
pageTitle: Text('Second Page Title'),
// Other properties...
),
];
},
);
Types of Decoration
Page Content Decoration
Purpose: Applies additional decorations to the modal page content only,
excluding the barrier.
Usage: Useful for modifying or enhancing the appearance and behavior of
the modal content without affecting the surrounding barrier and the
placement of the modal on the screen.
WidgetFunction(Widget)? pageContentDecorator;
Modal Decoration
Purpose: Applies additional decorations to the entire modal, including the
barrier and the page content.
Usage: Useful for wrapping the entire modal with a widget that manages the
state of the entire pages.
WidgetFunction(Widget)? modalDecorator;
Why use modalDecorator for state management?
When managing the state across the entire modal, for example by providing a
ChangeNotifierProvider for state management, it is important to wrap the
entire modal rather than just the page content. This ensures that the state
is accessible throughout the entire modal lifecycle and all its components.
Migration to v0.8.0
Versions before the v0.6.0 release used the decorator field to decorate as
modalDecorator. In release v0.6.0 and later, the decorator field was
used as pageContentDecorator. In v0.8.0, the decorator field was removed
and replaced with pageContentDecorator and modalDecorator.
Usage of WoltModalSheet Pages
The WoltModalSheet library provides three primary classes for constructing
modal sheet pages: WoltModalSheetPage, SliverWoltModalSheetPage, and
NonScrollingWoltModalSheetPage.
SliverWoltModalSheetPage
SliverWoltModalSheetPage is designed for layouts requiring the use of
Sliver widgets, such as lists, grids, and other scrollable content within
a modal sheet.
SliverWoltModalSheetPage(
mainContentSliversBuilder: (context) => [
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
// Your list items
}),
),
// Other sliver widgets...
],
// Additional page elements like pageTitle, topBarTitle, etc.
)
WoltModalSheetPage
WoltModalSheetPage simplifies the process of creating modal sheet pages
without needing to deal with slivers directly. This class is ideal for
straightforward widgets that don't require the advanced capabilities of
slivers and are not lazily built.
NonScrollingWoltModalSheetPage is designed to display content which is
flexible in height but unlikely to require scrolling. This class is ideal
for content that adapts to the available vertical space within the modal
sheet's maximum height. The main content can utilize the Flex layout model
of a Column for space management.
The package provides in-modal navigation capabilities, allowing for dynamic
transitions and stack manipulations directly within the modal. This section
details how to utilize these features effectively in your applications.
Managing Navigation Stack
he following methods facilitate the addition, removal, and modification of
pages within the navigation stack. These functionalities allow you to
dynamically adapt the navigation stack based on user interactions without
disrupting the currently displayed page.
Adding Pages to the Stack
You can append one or more pages to the navigation stack. This feature is
particularly useful for preloading pages or preparing navigation paths for
future steps without changing the currently displayed page.
These methods allow you to replace or remove specific pages within the stack.
If the page being modified is currently visible, appropriate adjustments are
made to ensure seamless navigation.
Replace a specific page
When you need to update or change the content of a specific page within the
stack, this method allows you to replace any page by its identifier without
altering the rest of the stack.
When the current page needs to be removed from the page list and replaced by
a new page, with a pagination animation at the same position in the list,
use replaceCurrentPage method.
When the overall context or the purpose of the current page is still
relevant, but you need to modify specific attributes of the current page, use
updateCurrentPage method.
This method enables you to selectively remove a page from the navigation
stack using its identifier. If the page being removed is the currently
displayed page, it will remove the page and adjust the currently displayed page
accordingly. If it is not the current page, it is removed from the stack
without impacting the current view.
WoltModalSheet.of(context).removePage(pageId);
Add or replace pages
This method updates the navigation stack by either adding new pages or
replacing all subsequent pages depending on the position of the current page.
If the current page is the last in the stack it simply appends the new pages.
However, if the current page is not the last, it replaces all pages
following it with the new ones. This functionality is particularly useful
for adapting the user's navigation path dynamically based on their
interactions with the currently displayed page or when making adjustments to
previously made decisions.
This subsection details the methods for moving within the modal's navigation
stack, allowing users to navigate through pages effectively.
Direct navigation
Navigate within the modal stack using specific methods to move directly to a
desired page. You can go to the next or previous page, jump to a page at a
specific index in the page list, navigate to a page by its unique identifier,
or even navigate based on the page type with optional filtering criteria.
// Move to the next pagebool movedNext = WoltModalSheet.of(context).showNext();
// Move to the previous pagebool movedPrevious = WoltModalSheet.of(context).showPrevious();
// Jump directly to a page at a specific indexbool navigatedByIndex = WoltModalSheet.of(context).showAtIndex(2);
// Navigate to a page by its unique identifierbool navigatedById = WoltModalSheet.of(context).showPageWithId(pageId);
// Navigate to a page by its typebool didShowPage = WoltModalSheet.of(context).showPage<MyCustomPage>();
// Navigate to a page of a certain type that meets additional criteriabool didShowFilteredPage = WoltModalSheet.of(context).showPage<MyCustomPage>(
where: (page) => page.someProperty == "desiredValue",
);
Pushing Pages
Using push methods, you can add one or more new pages to the end of the
navigation stack and navigate to the first of the newly added pages.
Using pop method, you can remove the last page of the navigation stack. If the
user is on the last page, the method will navigate to the previous page.
ValueNotifier<int> pageIndexNotifier and
ValueNotifier<WoltModalSheetPageListBuilder> pageListBuilderNotifier can
also be utilized for in-modal navigation and setting the modal navigation
stack. These notifiers dynamically manage the visible page index
and the page list, enhancing flexibility in response to the state changes.
Navigation by Page Index Notifier
ValueNotifier<int> pageIndexNotifier controls the visible page index,
enabling transitions between pages based on user interactions or other events.
// Example of setting up a modal with navigation between pages controlled by // a ValueNotifier<int>.final pageIndexNotifier = ValueNotifier(0); // Initializes the page indexWoltModalSheet.show(
context: context,
pageListBuilder: (modalSheetContext) => [
PageOne(onNextButtonPressed: () => pageIndexNotifier.value = 1),
PageTwo(onBackButtonPressed: () => pageIndexNotifier.value = pageIndexNotifier.value - 1),
],
pageIndexNotifier: pageIndexNotifier, // Tracks and updates the displayed page
);
Dynamic Page List Management by Page List Builder Notifier
ValueNotifier<WoltModalSheetPageListBuilder> pageListBuilderNotifier
manages updates to the modal's page list based on user interactions or other
app state changes. This is useful when implementing Navigator 2.0 for
declarative navigation scenarios. It is not suggested to mix the usage of
this notifier with the imperative navigation methods provided by the package.
This section outlines the design guidelines for the modal sheet component, including breakpoints and behavior across different devices. The guidelines are based on the Wolt design system specs.
Overview
The modal sheet component is used to display critical information or interactive elements to users in a non-intrusive way. This component adapts to different screen sizes and orientations.
Breakpoints
The modal sheet component adjusts its layout and positioning based on the
following breakpoints:
Breakpoint Large: Width ≥ 1400px
Breakpoint Medium: 768px ≤ Width < 1400px
Breakpoint Small: 524px ≤ Width < 768px
Breakpoint XSmall: Width < 524px
Modal Types
Alert Dialog
Used for critical information that requires immediate attention.
Must be dismissed by user interaction to ensure the alert is acknowledged.
Dialog
Used for single user actions or to convey information related to changes in state (success, errors).
Provides clear actions for users to acknowledge or dismiss the dialog.
We recommend to use dialogs in small, medium and large windows sizes.
Side Sheet
Used to focus users' attention on a specific task while keeping the context visible.
We recommend to use side sheets in small, medium and large windows sizes.
Bottom Sheet
Provides additional options or actions without leaving the current context.
We recommend to use bottom sheets only in xsmall window size. The side
sheet and dialog content should be shown in a bottom sheet in xsmall window.
Understanding the page elements
Each element within the WoltModalSheet has a role to play, offering context,
navigational assistance, and explicit action prompts to the user. By
understanding these elements and their roles, you can fully harness the power of
WoltModalSheet and create an intuitive and engaging user experience.
The structure is organized across layers on the z-axis:
Main Content Layer: The fundamental content of the page,
including the optional page title, optional hero image, and the main content,
which may be scrollable.
Top Bar Layer: Further above the main content layer, this layer
with the filled color includes the top bar title and may become hidden or
sticky based on scroll position and specific properties.
Navigation Bar Layer: Sitting at the top of the top bar layer on
z-axis, this transparent-background layer contains navigational widgets for
the interface, such as back or close buttons.
Sticky Action Bar Layer: Positioned at the top of the z axis,
this layer guides the user towards the next step, uses an optional gentle
gradient on top to hint that there is more content below ready for scrolling.
By employing these various layers, you can create an interactive and visually
appealing interface that resonates with users. Each layer contributes to the
overall coherence of the page, serving a specific purpose and enhancing the
overall user experience.
Navigation bar widgets
The navigation bar has a transparent background, and resides at the top of
the sheet, situated directly above the top bar on the z-axis. It includes
two specific widgets: the leading and the trailing. The leading widget
usually functions as the back button, enabling users to navigate to the
previous page. The trailing widget often serves as the close button, utilized to
close the modal sheet. The middle area is reserved and left empty for the
visibility of the top bar title.
The navigation bar widgets provide clear and intuitive navigational control,
differentiating themselves from the top bar by focusing specifically on
directional navigation within the interface.
Top bar and top bar title
The top bar layer sits above the main content layer and below the navigation
bar layer in z axis. It helps users grasping the context by displaying an
optional title. In scenarios where the sheet is filled with content
requiring scrolling, the top bar becomes visible as the user scrolls, and
replaces the page title. At this point, the top bar adopts a 'sticky'
position at the top, guaranteeing consistent visibility.
The top bar widget has a flexible design. When hasTopBarLayer is set to
false, the top bar and the top bar title will not be shown. If
isTopBarLayerAlwaysVisible set to true, the top bar will be always visible
regardless of the scroll position.
A custom top bar widget can be provided using the topBar field. In this
case, the topBarTitle field will be ignored, and will not be displayed.
The navigation bar widgets overlay above the top bar, and when the default
top bar widget is used in the page, the top bar title is symmetrically
framed between the leading and trailing navigation bar widgets.
Sticky action bar (SAB)
The Sticky Action Bar (SAB) guides the user towards the next step. Anchored to
the bottom of the view, the SAB elevates above the content with an optional
gentle gradient. This position guarantees that the action remains visible, subtly
hinting to the user that there is more content to be explored below the fold
by scrolling.
Hero image
An optional Hero Image can be positioned at the top of the main content. This
element immediately grabs the user's attention, effectively conveying the
primary theme or message of the content.
Page Title
An optional page title above the main content provides users with a quick
understanding of what to expect from the page. As the user scrolls, this title
becomes hidden, at which point the top bar title continues to serve this
context-providing purpose.
Main content
The main content delivers information according to the user need. It can be
scrollable to handle larger content. The content is built lazily to improve the
performance.
Here is an example that shows all the modal sheet elements in use:
Designer Collaboration Guide
To ensure seamless collaboration between designers and developers, we have
provided a Figma file dedicated to the WoltModalSheet package. This resource aims to guide designers through the process of creating and handing over designs that leverage the WoltModalSheet's capabilities effectively.
What is inside the Figma File
Example Design Specifications: Detailed design guidelines and
specifications mirroring those used internally at Wolt. These specs
illustrate how to prepare designs for development handoff, ensuring a
smooth transition from design to implementation.
Modal Types Specifications: This section showcases specifications for
different modal types, including dialog, alert dialog, side sheet, bottom
sheet modals. It covers essential details like dimensions, layout of
elements, and visual arrangements, providing clear instructions for
replicating these styles in your projects.
Motion Specs for Modal Interactions: To complement the
static design aspects, this section demonstrates motion
specifications for pagination and scrolling interactions. It includes
guidelines for animating transitions between modal sheet pages and within
the content scroll, enabling designers to specify dynamic, engaging user
experiences.
Customizable Animations
Developers can customize the page scrolling and pagination animations by
providing an instance of WoltModalSheetAnimationStyle class to
WoltModalSheetThemeData.
Design Philosophy: Dive into the creative thought process behind
WoltModalSheet's functionality in our blog post
. Explore how we tackled the design challenges to create an intuitive and
responsive experience.
Insights from FlutterCon'23
talk: We delved into both the design and developmental facets of this
package at the FlutterCon'23 conference. Catch the
enlightening recording of his talk
to understand the nuances.
Flutter&Friends talk: This is a lightening talk given at the
Flutter&Friends conference on September'23. It covers the design
guidelines and best practices by showing real-world examples highlighting
what to do—and what not to do. It also covers the technical details of the
implementation. The recording of the talk can be found here.