Skip to content

Commit 85c42ff

Browse files
committed
Bottom Modal is now scrollable
1 parent b966fc4 commit 85c42ff

File tree

3 files changed

+100
-146
lines changed

3 files changed

+100
-146
lines changed

lib/action/bottom_modal_actions.dart

+98-143
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import 'dart:developer';
2-
31
import 'package:ensemble/framework/action.dart';
42
import 'package:ensemble/framework/data_context.dart';
53
import 'package:ensemble/framework/error_handling.dart';
64
import 'package:ensemble/framework/event.dart';
7-
import 'package:ensemble/framework/extensions.dart';
5+
import 'package:ensemble/framework/model.dart';
86
import 'package:ensemble/framework/scope.dart';
9-
import 'package:ensemble/framework/view/context_scope_widget.dart';
107
import 'package:ensemble/framework/view/data_scope_widget.dart';
118
import 'package:ensemble/screen_controller.dart';
129
import 'package:ensemble/util/utils.dart';
@@ -15,77 +12,20 @@ import 'package:flutter/material.dart';
1512

1613
/// open a Modal Bottom Sheet
1714
class ShowBottomModalAction extends EnsembleAction {
18-
ShowBottomModalAction(
19-
{super.initiator,
20-
super.inputs,
21-
this.body,
22-
this.styles,
23-
this.options,
24-
this.onDismiss});
15+
ShowBottomModalAction({
16+
super.initiator,
17+
super.inputs,
18+
required this.body,
19+
required this.payload,
20+
this.onDismiss,
21+
});
22+
23+
static const defaultTopBorderRadius = Radius.circular(16);
2524

25+
final Map payload;
2626
final dynamic body;
27-
final Map<String, dynamic>? styles;
28-
final Map<String, dynamic>? options;
2927
final EnsembleAction? onDismiss;
3028

31-
// default height is size to content
32-
MainAxisSize getVerticalSize(scopeManager) =>
33-
MainAxisSize.values
34-
.from(scopeManager.dataContext.eval(styles?['verticalSize'])) ??
35-
MainAxisSize.min;
36-
37-
MainAxisAlignment getVerticalAlignment(scopeManager) {
38-
var alignment = scopeManager.dataContext.eval(styles?['verticalAlignment']);
39-
switch (alignment) {
40-
case 'top':
41-
return MainAxisAlignment.start;
42-
case 'bottom':
43-
return MainAxisAlignment.end;
44-
default:
45-
// if verticalSize is min this doesn't matter, but center align if max
46-
return MainAxisAlignment.center;
47-
}
48-
}
49-
50-
// default width is stretching 100%
51-
CrossAxisAlignment getHorizontalSize(scopeManager) {
52-
var size = MainAxisSize.values
53-
.from(scopeManager.dataContext.eval(styles?['horizontalSize'])) ??
54-
MainAxisSize.max;
55-
return size == MainAxisSize.min
56-
? CrossAxisAlignment.center
57-
: CrossAxisAlignment.stretch;
58-
}
59-
60-
Alignment? getHorizontalAlignment(scopeManager) {
61-
var alignment =
62-
scopeManager.dataContext.eval(styles?['horizontalAlignment']);
63-
switch (alignment) {
64-
case 'start':
65-
return Alignment.centerLeft;
66-
case 'center':
67-
return Alignment.center;
68-
case 'end':
69-
return Alignment.centerRight;
70-
default:
71-
return null;
72-
}
73-
}
74-
75-
Color? getBarrierColor(scopeManager) =>
76-
Utils.getColor(scopeManager.dataContext.eval(styles?['barrierColor']));
77-
78-
// default background is the dialog background
79-
Color? getBackgroundColor(scopeManager) =>
80-
Utils.getColor(scopeManager.dataContext.eval(styles?['backgroundColor']));
81-
82-
int? getTopBorderRadius(scopeManager) => Utils.optionalInt(
83-
scopeManager.dataContext.eval(styles?['topBorderRadius']));
84-
85-
bool getEnableDrag(scopeManager) =>
86-
Utils.getBool(scopeManager.dataContext.eval(styles?['enableDrag']),
87-
fallback: true);
88-
8929
factory ShowBottomModalAction.from({Invokable? initiator, Map? payload}) {
9030
dynamic body = payload?['body'] ?? payload?['widget'];
9131
if (payload == null || body == null) {
@@ -96,86 +36,101 @@ class ShowBottomModalAction extends EnsembleAction {
9636
initiator: initiator,
9737
inputs: Utils.getMap(payload['inputs']),
9838
body: body,
99-
styles: Utils.getMap(payload['styles']),
100-
options: Utils.getMap(payload['options']),
101-
onDismiss: EnsembleAction.fromYaml(payload['onDismiss']));
39+
onDismiss: EnsembleAction.fromYaml(payload['onDismiss']),
40+
payload: payload);
10241
}
10342

104-
@override
105-
Future<dynamic> execute(BuildContext context, ScopeManager scopeManager) {
106-
if (body == null) return Future.value(null);
43+
EdgeInsets? margin(scopeManager) =>
44+
Utils.optionalInsets(eval(payload["styles"]?["margin"], scopeManager));
10745

108-
// verticalSize: min | max
109-
// verticalAlignment: top | center | bottom
110-
// horizontalSize: min | max
111-
// horizontalAlignment: start | center | end
46+
EdgeInsets? padding(scopeManager) =>
47+
Utils.optionalInsets(eval(payload["styles"]?["padding"], scopeManager));
11248

113-
// topRadius: 15
114-
// backgroundColor
115-
// barrierColor
49+
EBorderRadius? borderRadius(scopeManager) => Utils.getBorderRadius(
50+
eval(payload["styles"]?["borderRadius"], scopeManager));
11651

117-
var topRadius =
118-
Radius.circular(getTopBorderRadius(scopeManager)?.toDouble() ?? 16);
119-
var horizontalAlignment = getHorizontalAlignment(scopeManager);
120-
var widget = scopeManager.buildWidgetFromDefinition(body);
52+
bool useSafeArea(scopeManager) =>
53+
Utils.getBool(eval(payload["styles"]?["useSafeArea"], scopeManager),
54+
fallback: false);
55+
56+
Color? getBarrierColor(scopeManager) =>
57+
Utils.getColor(eval(payload["styles"]?['barrierColor'], scopeManager));
12158

122-
var bodyWidget = Material(
123-
type: MaterialType.transparency,
124-
elevation: 16,
125-
child: Container(
126-
decoration: BoxDecoration(
127-
color: getBackgroundColor(scopeManager) ??
128-
Theme.of(context).dialogBackgroundColor,
129-
borderRadius:
130-
BorderRadius.only(topLeft: topRadius, topRight: topRadius)),
131-
child: Column(
132-
// vertical
133-
mainAxisSize: getVerticalSize(scopeManager),
134-
mainAxisAlignment: getVerticalAlignment(scopeManager),
135-
136-
// horizontal
137-
crossAxisAlignment: getHorizontalSize(scopeManager),
138-
children: [
139-
// account for the bottom notch
140-
SafeArea(
141-
bottom: false,
142-
child: horizontalAlignment != null
143-
? Align(alignment: horizontalAlignment, child: widget)
144-
: widget)
145-
])),
146-
);
147-
148-
showModalBottomSheet(
149-
context: context,
150-
// disable the default bottom sheet styling since we use our own
151-
backgroundColor: Colors.transparent,
152-
elevation: 16,
153-
154-
barrierColor: getBarrierColor(scopeManager),
155-
isScrollControlled: true,
156-
enableDrag: getEnableDrag(scopeManager),
157-
// padding to account for the keyboard when we have input widgets inside the modal
158-
builder: (modalContext) => Padding(
159-
padding: EdgeInsets.only(
160-
bottom: MediaQuery.of(modalContext).viewInsets.bottom,
161-
),
162-
// have a bottom modal scope widget so we can close the modal
163-
child: BottomModalScopeWidget(
164-
rootContext: modalContext,
165-
// create a new Data Scope since the bottom modal is placed in a different context tree (directly under MaterialApp)
166-
child: DataScopeWidget(
167-
scopeManager: scopeManager.createChildScope(),
168-
child: bodyWidget),
169-
)),
170-
).then((payload) {
171-
if (onDismiss != null) {
172-
return ScreenController().executeActionWithScope(
173-
context, scopeManager, onDismiss!,
174-
event: EnsembleEvent(null, data: payload));
175-
}
176-
});
59+
Color? getBackgroundColor(scopeManager) =>
60+
Utils.getColor(eval(payload["styles"]?['backgroundColor'], scopeManager));
61+
62+
bool? isScrollable(scopeManager) =>
63+
Utils.optionalBool(eval(payload["scrollable"], scopeManager));
64+
65+
@override
66+
Future<dynamic> execute(BuildContext context, ScopeManager scopeManager) {
67+
if (body != null) {
68+
showModalBottomSheet(
69+
context: context,
70+
// disable the default bottom sheet styling since we use our own
71+
backgroundColor: Colors.transparent,
72+
elevation: 0,
73+
74+
barrierColor: getBarrierColor(scopeManager),
75+
isScrollControlled: true,
76+
showDragHandle: true,
77+
enableDrag: true,
78+
// padding to account for the keyboard when we have input widgets inside the modal
79+
builder: (modalContext) => Padding(
80+
padding: EdgeInsets.only(
81+
bottom: MediaQuery.of(modalContext).viewInsets.bottom,
82+
),
83+
// have a bottom modal scope widget so we can close the modal
84+
child: BottomModalScopeWidget(
85+
rootContext: modalContext,
86+
// create a new Data Scope since the bottom modal is placed in a different context tree (directly under MaterialApp)
87+
child: DataScopeWidget(
88+
scopeManager: scopeManager.createChildScope(),
89+
child: getBodyWidget(scopeManager, context)),
90+
)),
91+
).then((payload) {
92+
if (onDismiss != null) {
93+
return ScreenController().executeActionWithScope(
94+
context, scopeManager, onDismiss!,
95+
event: EnsembleEvent(null, data: payload));
96+
}
97+
});
98+
}
17799
return Future.value(null);
178100
}
101+
102+
Widget getBodyWidget(ScopeManager scopeManager, BuildContext context) {
103+
var widget = scopeManager.buildWidgetFromDefinition(body);
104+
if (isScrollable(scopeManager) == true) {
105+
return DraggableScrollableSheet(
106+
expand: false,
107+
builder: (context, scrollController) =>
108+
buildRootContainer(scopeManager, context,
109+
child: SingleChildScrollView(
110+
controller: scrollController,
111+
child: widget,
112+
)));
113+
}
114+
return buildRootContainer(scopeManager, context, child: widget);
115+
}
116+
117+
// This is the root container where all the root styling happen
118+
Widget buildRootContainer(ScopeManager scopeManager, BuildContext context,
119+
{required Widget child}) {
120+
return Container(
121+
margin: margin(scopeManager),
122+
padding: padding(scopeManager),
123+
decoration: BoxDecoration(
124+
color: getBackgroundColor(scopeManager) ??
125+
Theme.of(context).dialogBackgroundColor,
126+
borderRadius: borderRadius(scopeManager)?.getValue() ??
127+
const BorderRadius.only(
128+
topLeft: defaultTopBorderRadius,
129+
topRight: defaultTopBorderRadius)),
130+
clipBehavior: Clip.antiAlias,
131+
width: double.infinity, // stretch width 100%
132+
child: useSafeArea(scopeManager) ? SafeArea(child: child) : child);
133+
}
179134
}
180135

181136
/// Dismiss the Bottom Modal (if the context is a descendant, no-op otherwise)

lib/framework/data_context.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class DataContext implements Context {
5959
final Map<String, dynamic> _contextMap = {};
6060

6161
get contextMap => _contextMap;
62-
final BuildContext buildContext;
62+
@Deprecated("do not use")
63+
BuildContext buildContext;
6364

6465
DataContext(
6566
{required this.buildContext,

lib/framework/view/context_scope_widget.dart

-2
This file was deleted.

0 commit comments

Comments
 (0)