diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 6499aea97..f0aa4c47c 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -5268,6 +5268,9 @@ "type": "boolean", "description": "Set visibility of calendar header. Default true." }, + "header": { + "$ref": "#/$defs/Widget" + }, "rowSpans": { "type": "array", "items": { @@ -5288,10 +5291,16 @@ "$ref": "#/$defs/Widget" } }, - "required": ["start", "end", "widget"] + "required": [ + "start", + "end", + "widget" + ] } }, - "required": ["span"] + "required": [ + "span" + ] } }, "headerTextStyle": { diff --git a/lib/action/invoke_api_action.dart b/lib/action/invoke_api_action.dart index df46f90be..0d1975e5d 100644 --- a/lib/action/invoke_api_action.dart +++ b/lib/action/invoke_api_action.dart @@ -224,13 +224,15 @@ class InvokeAPIController { EnsembleAction.fromYaml(apiDefinition['onError']); if (onErrorAction != null) { ScreenController().nowExecuteAction( - context, localizedContext, onErrorAction, apiMap, scopeManager, event: apiEvent); + context, localizedContext, onErrorAction, apiMap, scopeManager, + event: apiEvent); } // if our Action has onError, invoke that next if (action.onError != null) { ScreenController().nowExecuteAction( - context, localizedContext, action.onError!, apiMap, scopeManager, event: apiEvent); + context, localizedContext, action.onError!, apiMap, scopeManager, + event: apiEvent); } // silently fail if error handle is not defined? or should we alert user? diff --git a/lib/framework/data_context.dart b/lib/framework/data_context.dart index eb9f3a31e..0387d9e12 100644 --- a/lib/framework/data_context.dart +++ b/lib/framework/data_context.dart @@ -565,7 +565,7 @@ class Formatter with Invokable { 'prettyCurrency': (input) => InvokablePrimitive.prettyCurrency(input), 'prettyDuration': (input) => InvokablePrimitive.prettyDuration(input, locale: locale), - 'pluralize': pluralize + 'pluralize': pluralize, }; } diff --git a/lib/framework/ensemble_widget.dart b/lib/framework/ensemble_widget.dart index c1452cbce..f45ed55b2 100644 --- a/lib/framework/ensemble_widget.dart +++ b/lib/framework/ensemble_widget.dart @@ -1,16 +1,15 @@ import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/cupertino.dart'; -abstract class EnsembleWidget extends StatefulWidget { +abstract class EnsembleWidget + extends StatefulWidget { const EnsembleWidget(this.controller, {super.key}); final C controller; } abstract class EnsembleWidgetState extends State { void _update() { - setState(() { - - }); + setState(() {}); } @override @@ -32,4 +31,3 @@ abstract class EnsembleWidgetState extends State { super.dispose(); } } - diff --git a/lib/framework/scope.dart b/lib/framework/scope.dart index a1e659d21..819a82371 100644 --- a/lib/framework/scope.dart +++ b/lib/framework/scope.dart @@ -254,7 +254,8 @@ mixin ViewBuilder on IsScopeManager { if (model.inputs![param] != null) { // set the Custom Widget's inputs from parent scope evalPropertyAndRegisterBinding( - scopeManager._parent!, // widget inputs are set in the parent's scope + scopeManager + ._parent!, // widget inputs are set in the parent's scope payload.widget as Invokable, param, model.inputs![param]); @@ -276,7 +277,8 @@ mixin ViewBuilder on IsScopeManager { // set props and styles on the widget. At this stage the widget // has not been attached, so no worries about ValueNotifier for (String key in model.props.keys) { - if (InvokableController.getSettableProperties(invokable).contains(key)) { + if (InvokableController.getSettableProperties(invokable) + .contains(key)) { if (_isPassthroughProperty(key, invokable)) { InvokableController.setProperty(invokable, key, model.props[key]); } else { @@ -286,9 +288,11 @@ mixin ViewBuilder on IsScopeManager { } } for (String key in model.styles.keys) { - if (InvokableController.getSettableProperties(invokable).contains(key)) { + if (InvokableController.getSettableProperties(invokable) + .contains(key)) { if (_isPassthroughProperty(key, invokable)) { - InvokableController.setProperty(invokable, key, model.styles[key]); + InvokableController.setProperty( + invokable, key, model.styles[key]); } else { evalPropertyAndRegisterBinding( scopeManager, invokable, key, model.styles[key]); diff --git a/lib/framework/widget/view_util.dart b/lib/framework/widget/view_util.dart index 79cc5cadb..d399c3367 100644 --- a/lib/framework/widget/view_util.dart +++ b/lib/framework/widget/view_util.dart @@ -219,8 +219,7 @@ class ViewUtil { EnsembleController? previousController; String? id = model.props['id']?.toString(); if (id != null) { - dynamic controller = - scopeNode.scope.dataContext.getContextById(id); + dynamic controller = scopeNode.scope.dataContext.getContextById(id); if (controller is EnsembleController) { previousController = controller; } diff --git a/lib/widget/calendar.dart b/lib/widget/calendar.dart index 11515d63f..ae7e00fd7 100644 --- a/lib/widget/calendar.dart +++ b/lib/widget/calendar.dart @@ -40,13 +40,13 @@ class EnsembleCalendar extends StatefulWidget 'markedCell': () => _controller.markedDays.value .map((e) => e.toIso8601DateString()) .toList(), - 'disableCell': () => _controller.disableDays.value + 'disabledCell': () => _controller.disableDays.value .map((e) => e.toIso8601DateString()) .toList(), 'rangeStart': () => _controller.rangeStart?.toIso8601DateString(), 'rangeEnd': () => _controller.rangeEnd?.toIso8601DateString(), 'range': () => _controller.range, - 'focusDay': () => _controller.focusedDay.value, + 'focusDate': () => _controller.focusedDay.value, }; } @@ -54,12 +54,20 @@ class EnsembleCalendar extends StatefulWidget Map methods() { return { 'selectCell': (value) => _selectCell(value), + 'toggleSelectCell': (value) => _toggleSelectedCell(value), + 'unSelectCell': (value) => _unSelectCell(value), 'markCell': (singleDate) => _markCell(singleDate), + 'unMarkCell': (singleDate) => _unMarkCell(singleDate), + 'toggleMarkCell': (singleDate) => _toggleMarkCell(singleDate), 'disableCell': (value) => _disableCell(value), + 'enableCell': (value) => _enableCell(value), + 'toggleDisableCell': (value) => _toggleDisableCell(value), 'previous': (value) => _controller.pageController?.previousPage( duration: const Duration(milliseconds: 300), curve: Curves.easeOut), 'next': (value) => _controller.pageController?.nextPage( - duration: const Duration(milliseconds: 300), curve: Curves.easeOut) + duration: const Duration(milliseconds: 300), curve: Curves.easeOut), + 'addRowSpan': (value) => setRowSpan(value), + 'header': (value) => _controller.header, }; } @@ -133,6 +141,40 @@ class EnsembleCalendar extends StatefulWidget final rawDate = _getDate(value); if (rawDate == null) return; + HashSet updatedDisabledDays = + HashSet.from(_controller.disableDays.value); + + for (var date in rawDate) { + updatedDisabledDays.add(date); + } + + _controller.rangeStart = null; + _controller.rangeEnd = null; + _controller.range = null; + _controller.disableDays.value = updatedDisabledDays; + } + + void _enableCell(dynamic value) { + final rawDate = _getDate(value); + if (rawDate == null) return; + + HashSet updatedDisabledDays = + HashSet.from(_controller.disableDays.value); + + for (var date in rawDate) { + updatedDisabledDays.remove(date); + } + + _controller.rangeStart = null; + _controller.rangeEnd = null; + _controller.range = null; + _controller.disableDays.value = updatedDisabledDays; + } + + void _toggleDisableCell(dynamic value) { + final rawDate = _getDate(value); + if (rawDate == null) return; + HashSet updatedDisabledDays = HashSet.from(_controller.disableDays.value); @@ -154,6 +196,40 @@ class EnsembleCalendar extends StatefulWidget final rawDate = _getDate(value); if (rawDate == null) return; + HashSet updatedDisabledDays = + HashSet.from(_controller.selectedDays.value); + + for (var date in rawDate) { + updatedDisabledDays.add(date); + } + + _controller.rangeStart = null; + _controller.rangeEnd = null; + _controller.range = null; + _controller.selectedDays.value = updatedDisabledDays; + } + + void _unSelectCell(dynamic value) { + final rawDate = _getDate(value); + if (rawDate == null) return; + + HashSet updatedDisabledDays = + HashSet.from(_controller.selectedDays.value); + + for (var date in rawDate) { + updatedDisabledDays.remove(date); + } + + _controller.rangeStart = null; + _controller.rangeEnd = null; + _controller.range = null; + _controller.selectedDays.value = updatedDisabledDays; + } + + void _toggleSelectedCell(dynamic value) { + final rawDate = _getDate(value); + if (rawDate == null) return; + HashSet updatedDisabledDays = HashSet.from(_controller.selectedDays.value); @@ -171,10 +247,52 @@ class EnsembleCalendar extends StatefulWidget _controller.selectedDays.value = updatedDisabledDays; } + void _unMarkCell(dynamic value) { + final rawDate = _getDate(value); + if (rawDate == null) return; + + HashSet updatedMarkDays = + HashSet.from(_controller.markedDays.value); + HashSet updatedSelectedDays = + HashSet.from(_controller.selectedDays.value); + + for (var date in rawDate) { + updatedMarkDays.remove(date); + updatedSelectedDays.remove(date); + } + + _controller.rangeStart = null; + _controller.rangeEnd = null; + _controller.range = null; + _controller.markedDays.value = updatedMarkDays; + _controller.selectedDays.value = updatedSelectedDays; + } + void _markCell(dynamic value) { final rawDate = _getDate(value); if (rawDate == null) return; + HashSet updatedMarkDays = + HashSet.from(_controller.markedDays.value); + HashSet updatedSelectedDays = + HashSet.from(_controller.selectedDays.value); + + for (var date in rawDate) { + updatedMarkDays.add(date); + updatedSelectedDays.remove(date); + } + + _controller.rangeStart = null; + _controller.rangeEnd = null; + _controller.range = null; + _controller.markedDays.value = updatedMarkDays; + _controller.selectedDays.value = updatedSelectedDays; + } + + void _toggleMarkCell(dynamic value) { + final rawDate = _getDate(value); + if (rawDate == null) return; + HashSet updatedMarkDays = HashSet.from(_controller.markedDays.value); HashSet updatedSelectedDays = @@ -197,14 +315,17 @@ class EnsembleCalendar extends StatefulWidget } void setRowSpan(dynamic data) { - if (data is YamlMap) { + final List spans = List.from(_controller.rowSpans.value); + if (data is YamlMap || data is Map) { final rowSpan = RowSpanConfig( startDay: Utils.getDate(data['start']), endDay: Utils.getDate(data['end']), widget: data['widget'], + inputs: data['inputs'], ); - _controller.rowSpans.add(rowSpan); + spans.add(rowSpan); } + _controller.rowSpans.value = spans; } void setRangeData(dynamic data) { @@ -244,13 +365,13 @@ class RowSpanConfig { DateTime? startDay; DateTime? endDay; dynamic widget; - Color? backgroundColor; + Map? inputs; RowSpanConfig({ this.startDay, this.endDay, this.widget, - this.backgroundColor, + this.inputs, }); bool get isValid => startDay != null && endDay != null; @@ -309,10 +430,11 @@ class CalendarController extends WidgetController { EnsembleAction? onRangeStart; Color? highlightColor; bool headerVisible = true; + dynamic header; DateTime? firstDay; DateTime? lastDay; final ValueNotifier focusedDay = ValueNotifier(DateTime.now()); - List rowSpans = []; + ValueNotifier> rowSpans = ValueNotifier([]); TextStyle? headerTextStyle; final ValueNotifier> markedDays = ValueNotifier( @@ -356,6 +478,10 @@ class CalendarState extends WidgetState { setState(() {}); }); + widget._controller.rowSpans.addListener(() { + setState(() {}); + }); + super.initState(); } @@ -428,22 +554,29 @@ class CalendarState extends WidgetState { ValueListenableBuilder( valueListenable: widget._controller.focusedDay, builder: (context, value, _) { - return _CalendarHeader( - focusedDay: value, - headerStyle: widget._controller.headerTextStyle, - onLeftArrowTap: () { - widget._controller.pageController?.previousPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeOut, - ); - }, - onRightArrowTap: () { - widget._controller.pageController?.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeOut, + Widget? header; + + if (widget._controller.header != null) { + header = widgetBuilder(context, widget._controller.header, {}); + } + + return header ?? + _CalendarHeader( + focusedDay: value, + headerStyle: widget._controller.headerTextStyle, + onLeftArrowTap: () { + widget._controller.pageController?.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + }, + onRightArrowTap: () { + widget._controller.pageController?.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + }, ); - }, - ); }, ), TableCalendar( @@ -470,18 +603,22 @@ class CalendarState extends WidgetState { daysOfWeekVisible: true, overlayRanges: getOverlayRange(), calendarBuilders: CalendarBuilders( - overlayBuilder: widget._controller.rowSpans.isEmpty + overlayBuilder: widget._controller.rowSpans.value.isEmpty ? null : (context, range) { final spans = widget._controller.rowSpans; - for (var span in spans) { + for (var span in spans.value) { if (span.startDay == null || span.endDay == null) { return const SizedBox.shrink(); } if (DateTimeRange( start: span.startDay!, end: span.endDay!) == range) { - return widgetBuilder(context, span.widget, {}); + return widgetBuilder( + context, + span.widget, + span.inputs?.cast() ?? {}, + ); } } return const SizedBox.shrink(); @@ -595,7 +732,7 @@ class CalendarState extends WidgetState { List getOverlayRange() { final overlayRange = []; - for (var span in widget._controller.rowSpans) { + for (var span in widget._controller.rowSpans.value) { if (span.endDay != null && span.startDay != null) { overlayRange .add(DateTimeRange(start: span.startDay!, end: span.endDay!)); diff --git a/lib/widget/widget_registry.dart b/lib/widget/widget_registry.dart index b88414bb7..40f27b91a 100644 --- a/lib/widget/widget_registry.dart +++ b/lib/widget/widget_registry.dart @@ -67,7 +67,7 @@ class WidgetRegistry { /// all statically-known widgets should be registered here. /// widgets can be dynamically registered (or overridden) by calling registerWidget() final Map _registeredWidgets = { - // TextWidget.type: TextWidget.build, + // TextWidget.type: TextWidget.build, }; /// register or override a widget @@ -77,10 +77,8 @@ class WidgetRegistry { Map get widgetMap => _registeredWidgets; - /// Legacy: To be moved to _registeredWidgets; - static Map get legacyWidgetMap => - { + static Map get legacyWidgetMap => { EnsembleText.type: () => EnsembleText(), Markdown.type: () => Markdown(), EnsembleHtml.type: () => EnsembleHtml(),