Skip to content

Commit 3d9795a

Browse files
committed
Present show-dialog canvases as bottom sheets and side sheets
showLayoutDialog pushes a LayoutPresentationRoute that presents the canvas per ShowDialogAction.presentation: - dialog: centered at its design size, now scaling and fading in (0.96 to 1, 320 ms easeOutCubic). - bottomSheet: full width at the bottom, top corners radius 10, a 40x4 grab handle 8 px from the top, as tall as the canvas (its content when it shrink-wraps) up to maxHeightFactor of the screen; slides up. - sideSheet: full height on the right, sheetWidth (or the canvas's design width) wide, with a shadow; slides in from the right. The barrier color, barrier dismissal, route params and Navigate back results work as before. A new CanvasSizing.fillWidth sizes the bottom sheet's canvas. What a show-dialog's canvas passes back on Navigate back becomes the chain's result for the actions after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 08d64ad commit 3d9795a

4 files changed

Lines changed: 804 additions & 19 deletions

File tree

‎lib/src/functions/functions_repository.dart‎

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import 'package:url_launcher/url_launcher.dart';
1212

1313
import '../../codelessly_sdk.dart';
1414
import '../logging/stat_tracker.dart';
15-
import '../ui/codelessly_dialog_widget.dart';
15+
import '../ui/layout_presentation_route.dart';
1616
import 'action_chain.dart';
1717
import 'app_auth.dart';
1818
import 'cloud_files.dart';
@@ -560,18 +560,27 @@ class FunctionsRepository {
560560
}
561561
}
562562

563+
/// Shows [action]'s canvas. What its "Navigate back" passes back, e.g. the
564+
/// values of a form in a sheet, is the `${result}` of the actions after it.
563565
static Future<void> showDialogAction(
564566
BuildContext context,
565567
ShowDialogAction action,
566568
) async {
567-
await showLayoutDialog(
569+
final Object? result = await showLayoutDialog(
568570
context,
569571
canvasId: action.destinationId,
570572
params: action.params,
571573
barrierDismissible: action.barrierDismissible,
572574
barrierColor: action.barrierColor?.toFlutterColor(),
573575
showCloseButton: action.showCloseButton,
576+
presentation: action.presentation,
577+
sheetWidth: action.sheetWidth,
578+
maxHeightFactor: action.maxHeightFactor,
579+
showDragHandle: action.showDragHandle,
574580
);
581+
if (result case final Map values when values.isNotEmpty) {
582+
ActionChain.setResult(values);
583+
}
575584
}
576585

577586
/// The id of the published layout that holds the canvas [canvasId], if any.
@@ -580,16 +589,21 @@ class FunctionsRepository {
580589
.firstWhereOrNull((layout) => layout.canvasIds.contains(canvasId))
581590
?.id;
582591

583-
/// Shows the canvas [canvasId] as a dialog and completes with what it was
584-
/// closed with: the params of the "Navigate back" action that closed it, or
585-
/// null when it was dismissed.
592+
/// Shows the canvas [canvasId] as a dialog, a bottom sheet or a side sheet
593+
/// (see [presentation]) and completes with what it was closed with: the
594+
/// params of the "Navigate back" action that closed it, or null when it was
595+
/// dismissed.
586596
static Future<Object?> showLayoutDialog(
587597
BuildContext context, {
588598
required String canvasId,
589599
Map<String, dynamic> params = const {},
590600
bool barrierDismissible = true,
591601
Color? barrierColor,
592602
bool showCloseButton = false,
603+
DialogPresentation presentation = DialogPresentation.dialog,
604+
double? sheetWidth,
605+
double maxHeightFactor = 0.92,
606+
bool showDragHandle = true,
593607
}) async {
594608
final ScopedValues scopedValues = ActionChain.scopedValuesOf(context);
595609
final parsedParams = substituteVariablesInMap(params, scopedValues);
@@ -632,18 +646,29 @@ class FunctionsRepository {
632646
// A chain triggered in the dialog can finish on this layout after the
633647
// dialog closes (e.g. Navigate back, then a toast).
634648
final List<BuildContext> openerHosts = ActionChainOpener.hostsFor(context);
635-
final Object? result = await showDialog<Object?>(
636-
context: context,
637-
useRootNavigator: false,
638-
barrierDismissible: barrierDismissible,
639-
barrierColor: barrierColor,
640-
routeSettings: RouteSettings(arguments: parsedParams),
641-
builder: (context) => ActionChainOpener(
642-
openerHosts: openerHosts,
643-
child: CodelesslyDialogWidget(
644-
showCloseButton: showCloseButton,
645-
builder: (context) =>
646-
CodelesslyWidget(codelessly: codelessly, layoutID: layoutId),
649+
final BaseNode? canvas = codelessly
650+
.dataManager
651+
.publishModel
652+
?.layouts[layoutId]
653+
?.canvases[canvasId]?[canvasId];
654+
final Object? result = await Navigator.of(context).push<Object?>(
655+
LayoutPresentationRoute<Object?>(
656+
context: context,
657+
presentation: presentation,
658+
barrierDismissible: barrierDismissible,
659+
barrierColor:
660+
barrierColor ??
661+
Theme.of(context).dialogTheme.barrierColor ??
662+
Colors.black54,
663+
showCloseButton: showCloseButton,
664+
sheetWidth: sheetWidth,
665+
designWidth: canvas?.basicBoxLocal.width,
666+
maxHeightFactor: maxHeightFactor,
667+
showDragHandle: showDragHandle,
668+
settings: RouteSettings(arguments: parsedParams),
669+
builder: (context) => ActionChainOpener(
670+
openerHosts: openerHosts,
671+
child: CodelesslyWidget(codelessly: codelessly, layoutID: layoutId),
647672
),
648673
),
649674
);

‎lib/src/transformers/node_transformers/passive_canvas_transformer.dart‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,11 @@ enum CanvasSizing {
440440
/// given. Used where the host only centers the layout and lets it choose
441441
/// its size, such as a dialog.
442442
design,
443+
444+
/// The canvas fills the width it is given and keeps its design height (or
445+
/// its content's), within the height it is given. Used by a bottom sheet,
446+
/// which spans the screen and is as tall as its canvas.
447+
fillWidth,
443448
}
444449

445450
/// Sets the [CanvasSizing] of the canvases below it. Without one, canvases
@@ -514,8 +519,11 @@ class CanvasViewport extends StatelessWidget {
514519
node.verticalFit == SizeFit.shrinkWrap &&
515520
node.scaleMode == ScaleMode.responsive &&
516521
!node.isAScaffold;
517-
if (sizing == CanvasSizing.design) {
518-
final double width = constraints.constrainWidth(node.basicBoxLocal.width);
522+
if (sizing != CanvasSizing.fill) {
523+
final double width =
524+
sizing == CanvasSizing.fillWidth && constraints.hasBoundedWidth
525+
? constraints.maxWidth
526+
: constraints.constrainWidth(node.basicBoxLocal.width);
519527
if (contentHeight) return (width: width, height: null);
520528
final double height = node.scaleMode == ScaleMode.autoScale
521529
? autoScaleHeight(node, width)

0 commit comments

Comments
 (0)