1- import 'dart:developer' ;
2-
31import 'package:ensemble/framework/action.dart' ;
42import 'package:ensemble/framework/data_context.dart' ;
53import 'package:ensemble/framework/error_handling.dart' ;
64import 'package:ensemble/framework/event.dart' ;
7- import 'package:ensemble/framework/extensions .dart' ;
5+ import 'package:ensemble/framework/model .dart' ;
86import 'package:ensemble/framework/scope.dart' ;
9- import 'package:ensemble/framework/view/context_scope_widget.dart' ;
107import 'package:ensemble/framework/view/data_scope_widget.dart' ;
118import 'package:ensemble/screen_controller.dart' ;
129import 'package:ensemble/util/utils.dart' ;
@@ -15,77 +12,20 @@ import 'package:flutter/material.dart';
1512
1613/// open a Modal Bottom Sheet
1714class 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)
0 commit comments