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

[Breaking Change] Introduce modal and page content decorators #267

Merged
merged 3 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
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
128 changes: 118 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ for page transitions, and scrollable content within each page.
- [Modal Types](#modal-types)
* [Defining Custom Modal Types](#defining-custom-modal-types)
* [Modal Type Responsiveness](#modal-type-responsiveness)
* [Migration from v0.6.0 to v0.7.0](#migration-from-v060-to-v070)
- [Decorating modal types, modal, and pages](#decorating-modal-types-modal-and-pages)
* [Decoration Approaches](#decoration-approaches)
+ [Modal Type Level Decoration](#modal-type-level-decoration)
+ [Modal Level Decoration](#modal-level-decoration)
* [Types of Decoration](#types-of-decoration)
+ [Page Content Decoration](#page-content-decoration)
+ [Modal Decoration](#modal-decoration)
- [Why use modalDecorator for state management?](#why-use-modaldecorator-for-state-management)
* [Migration to v0.8.0](#migration-to-v080)
- [Usage of WoltModalSheet Pages](#usage-of-woltmodalsheet-pages)
* [SliverWoltModalSheetPage](#sliverwoltmodalsheetpage)
* [WoltModalSheetPage](#woltmodalsheetpage)
Expand Down Expand Up @@ -334,15 +342,115 @@ dynamical screen width:

![Responsive modals](https://github.com/woltapp/wolt_modal_sheet/blob/main/doc/ss_type_builder.gif?raw=true)

### Migration from v0.6.0 to v0.7.0
## 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,
enhancing or modifying their behavior.

- WoltModalType.bottomSheet is now WoltModalType.bottomSheet()
- WoltModalType.dialog is now WoltModalType.dialog()
- The `transitionDuration`, `bottomSheetTransitionAnimation`,
`dialogTransitionAnimation`,`minDialogWidth`,`maxDialogWidth`,
`minPageHeight`,`maxPageHeight` are removed from `WoltModalSheet.show()`.
These values can be set in `WoltBottomSheetType`, `WoltDialogType` or a
custom modal type instead.
### 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, you extend the corresponding
`WoltModalType` class and override the related methods.

Example:

```dart
class MyCustomBottomSheetType extends WoltBottomSheetType {
const MyCustomBottomSheetType() : super();

@override
Widget decoratePageContent(BuildContext context, Widget child, bool useSafeArea) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: child,
);
}

@override
Widget decorateModal(BuildContext context, Widget modal, bool useSafeArea) {
return useSafeArea ? SafeArea(child: modal) : modal;
}
}
```

#### Modal Level Decoration

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:

```dart
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.
return ChangeNotifierProvider<StoreOnlineViewModel>.value(
value: viewModel,
builder: (_, __) => child,
);
},
pageListBuilder: (context) => [
// Your pages here
],
);
```

### 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.

```dart
Widget Function(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.

```dart
Widget Function(Widget)? modalDecorator;
```

##### Why use modalDecorator for state management?

When managing the state across the entire modal, such as 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

Expand Down Expand Up @@ -1015,7 +1123,7 @@ current state:

WoltModalSheet.show(
context: context,
decorator: (child) {
pageContentDecorator: (child) {
return ChangeNotifierProvider<StoreOnlineViewModel>.value(
value: model,
builder: (_, __) => child,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'package:coffee_maker/home/online/view_model/store_online_view_model.dart';
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class AddWaterDescriptionModalPage {
AddWaterDescriptionModalPage._();

static WoltModalSheetPage build(String coffeeOrderId) {
static WoltModalSheetPage build(
String coffeeOrderId, {
required VoidCallback onCancelOrder,
}) {
return WoltModalSheetPage(
heroImage: const Image(
image: AssetImage('lib/assets/images/add_water_description.png'),
Expand All @@ -18,13 +19,8 @@ class AddWaterDescriptionModalPage {
child: Column(
children: [
Builder(builder: (context) {
final model = context.read<StoreOnlineViewModel>();

return WoltElevatedButton(
onPressed: () {
model.onCoffeeOrderStatusChange(coffeeOrderId);
Navigator.pop(context);
},
onPressed: onCancelOrder,
theme: WoltElevatedButtonTheme.secondary,
child: const Text('Cancel order'),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import 'package:coffee_maker/entities/coffee_maker_step.dart';
import 'package:coffee_maker/home/online/modal_pages/add_water/widgets/water_quantity_temperature_input.dart';
import 'package:coffee_maker/home/online/modal_pages/add_water/widgets/water_source_list.dart';
import 'package:coffee_maker/home/online/view_model/store_online_view_model.dart';
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class WaterSettingsModalPage {
WaterSettingsModalPage._();

static WoltModalSheetPage build(String coffeeOrderId) {
static WoltModalSheetPage build(
String coffeeOrderId, {
required VoidCallback onFinishAddingWater,
}) {
final buttonEnabledListener = ValueNotifier(false);
const pageTitle = 'Water settings';

Expand All @@ -20,18 +20,11 @@ class WaterSettingsModalPage {
builder: (_, isEnabled, __) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Builder(builder: (context) {
final model = context.read<StoreOnlineViewModel>();
return WoltElevatedButton(
onPressed: () {
model.onCoffeeOrderStatusChange(
coffeeOrderId, CoffeeMakerStep.ready);
Navigator.pop(context);
},
enabled: isEnabled,
child: const Text('Finish adding water'),
);
}),
child: WoltElevatedButton(
onPressed: onFinishAddingWater,
enabled: isEnabled,
child: const Text('Finish adding water'),
),
);
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import 'package:coffee_maker/entities/coffee_maker_step.dart';
import 'package:coffee_maker/home/online/view_model/store_online_view_model.dart';
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class GrindOrRejectModalPage {
GrindOrRejectModalPage._();

static WoltModalSheetPage build({required String coffeeOrderId}) {
static WoltModalSheetPage build({
required String coffeeOrderId,
required VoidCallback onGrindCoffeeTapped,
}) {
return WoltModalSheetPage(
stickyActionBar: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
Expand All @@ -23,17 +23,10 @@ class GrindOrRejectModalPage {
);
}),
const SizedBox(height: 8),
Builder(builder: (context) {
final model = context.read<StoreOnlineViewModel>();
return WoltElevatedButton(
onPressed: () {
model.onCoffeeOrderStatusChange(
coffeeOrderId, CoffeeMakerStep.addWater);
Navigator.pop(context);
},
child: const Text('Start grinding'),
);
}),
WoltElevatedButton(
onPressed: onGrindCoffeeTapped,
child: const Text('Start grinding'),
),
],
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import 'package:coffee_maker/home/online/modal_pages/grind/reject_order_reason.dart';
import 'package:coffee_maker/home/online/view_model/store_online_view_model.dart';
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class RejectOrderModalPage {
RejectOrderModalPage._();

static WoltModalSheetPage build({required String coffeeOrderId}) {
static WoltModalSheetPage build({
required String coffeeOrderId,
required VoidCallback onRejectOrderTapped,
}) {
final buttonEnabledListener = ValueNotifier(false);

return WoltModalSheetPage(
Expand All @@ -18,12 +19,8 @@ class RejectOrderModalPage {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Builder(builder: (context) {
final model = context.read<StoreOnlineViewModel>();
return WoltElevatedButton(
onPressed: () {
model.onCoffeeOrderStatusChange(coffeeOrderId);
Navigator.pop(context);
},
onPressed: onRejectOrderTapped,
theme: WoltElevatedButtonTheme.secondary,
colorName: WoltColorName.red,
enabled: isEnabled,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import 'package:coffee_maker/home/online/modal_pages/ready/extra_recommendation.dart';
import 'package:coffee_maker/home/online/modal_pages/ready/extra_recommendation_tile.dart';
import 'package:coffee_maker/home/online/view_model/store_online_view_model.dart';
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class OfferRecommendationModalPage {
OfferRecommendationModalPage._();

static SliverWoltModalSheetPage build({required String coffeeOrderId}) {
static SliverWoltModalSheetPage build({
required String coffeeOrderId,
required VoidCallback onServeWithRecommendation,
}) {
final selectedItemCountListener = ValueNotifier(0);
const pageTitle = 'Recommendations';
const allRecommendations = ExtraRecommendation.values;
Expand All @@ -30,17 +31,11 @@ class OfferRecommendationModalPage {
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Builder(builder: (context) {
final model = context.read<StoreOnlineViewModel>();
return WoltElevatedButton(
onPressed: () {
model.onCoffeeOrderStatusChange(coffeeOrderId);
Navigator.pop(context);
},
enabled: count > 0,
child: Text(buttonText),
);
}),
child: WoltElevatedButton(
onPressed: onServeWithRecommendation,
enabled: count > 0,
child: Text(buttonText),
),
);
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'package:coffee_maker/home/online/view_model/store_online_view_model.dart';
import 'package:demo_ui_components/demo_ui_components.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wolt_modal_sheet/wolt_modal_sheet.dart';

class ServeOrOfferModalPage {
ServeOrOfferModalPage._();

static WoltModalSheetPage build({required String coffeeOrderId}) {
static WoltModalSheetPage build({
required String coffeeOrderId,
required VoidCallback onServeCoffeeTapped,
}) {
return WoltModalSheetPage(
heroImage: const Image(
image: AssetImage('lib/assets/images/coffee_is_ready.png'),
Expand All @@ -17,17 +18,11 @@ class ServeOrOfferModalPage {
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(
children: [
Builder(builder: (context) {
final model = context.read<StoreOnlineViewModel>();
return WoltElevatedButton(
onPressed: () {
model.onCoffeeOrderStatusChange(coffeeOrderId);
Navigator.pop(context);
},
theme: WoltElevatedButtonTheme.secondary,
child: const Text('Serve coffee'),
);
}),
WoltElevatedButton(
onPressed: onServeCoffeeTapped,
theme: WoltElevatedButtonTheme.secondary,
child: const Text('Serve coffee'),
),
const SizedBox(height: 8),
Builder(builder: (context) {
return WoltElevatedButton(
Expand Down
Loading
Loading