Skip to content

Commit

Permalink
Merge branch 'main' into input-field-update
Browse files Browse the repository at this point in the history
  • Loading branch information
vinothvino42 authored Feb 19, 2024
2 parents 93e0f00 + 35b0a00 commit de0d4e7
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 5 deletions.
1 change: 0 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.10.6'

- name: Lint
run: dart format --output=none --set-exit-if-changed .
Expand Down
129 changes: 128 additions & 1 deletion lib/framework/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,128 @@ class ExecuteCodeAction extends EnsembleAction {
ViewUtil.optDefinition((payload as YamlMap).nodes['body']));
}
}
class ExecuteActionGroupAction extends EnsembleAction {
ExecuteActionGroupAction({super.initiator, required this.actions});
List<EnsembleAction> actions;
factory ExecuteActionGroupAction.fromYaml({Invokable? initiator, Map? payload}) {
if (payload == null || payload['actions'] == null) {
throw LanguageError(
"${ActionType.executeActionGroup.name} requires a 'actions' list.");
}

if (payload['actions'] is! List<dynamic>) {
throw LanguageError(
"${ActionType.executeActionGroup.name} requires a 'actions' list.");
}
List<dynamic> actions = payload['actions'] as List<dynamic>;
if (actions == null || actions.isEmpty) {
throw LanguageError(
"${ActionType.executeActionGroup.name} requires a 'actions' list.");
}
List<EnsembleAction> ensembleActions = [];
for (var action in actions) {
EnsembleAction? ensembleAction = EnsembleAction.fromYaml(action,
initiator: initiator);
if ( ensembleAction == null ) {
throw LanguageError(
"$action under ${ActionType.executeActionGroup.name} is not a valid action");
}
if (ensembleAction != null) {
ensembleActions.add(ensembleAction);
}
}
return ExecuteActionGroupAction(
initiator: initiator,
actions: ensembleActions);
}
factory ExecuteActionGroupAction.from(dynamic payload) =>
ExecuteActionGroupAction.fromYaml(payload: Utils.getYamlMap(payload));
@override
Future<void> execute(BuildContext context, ScopeManager scopeManager) {
// Map each action into a Future by calling execute on it, starting all actions in parallel
var actionFutures = actions.map((action) {
return ScreenController().executeActionWithScope(context, scopeManager, action);
});

// Wait for all action Futures to complete and return the resulting Future
return Future.wait(actionFutures);
}

}
class ExecuteConditionalActionAction extends EnsembleAction {
List<dynamic> conditions;
ExecuteConditionalActionAction({super.initiator, required this.conditions});
factory ExecuteConditionalActionAction.fromYaml({Invokable? initiator, Map? payload}) {
if (payload == null || payload['conditions'] == null) {
throw LanguageError(
"${ActionType.executeConditionalAction.name} requires a 'conditions' list.");
}
if ( payload['conditions'] is! List<dynamic> ) {
throw LanguageError(
"${ActionType.executeConditionalAction.name} requires a 'conditions' list.");
}
List<dynamic> conditions = payload['conditions'] as List<dynamic>;
if ( conditions == null || conditions.isEmpty ) {
throw LanguageError(
"${ActionType.executeConditionalAction.name} requires a 'conditions' list.");
}
if ( conditions.first.containsKey('if') == false ) {
throw LanguageError(
"${ActionType.executeConditionalAction.name} requires a 'conditions' list with the first condition being an 'if' condition.");
}
// Iterate over the conditions list starting from the second element
for (int i = 1; i < conditions.length; i++) {
if (conditions[i] is Map && conditions[i].containsKey('if')) {
throw LanguageError("${ActionType.executeConditionalAction.name} requires that only the first condition contains an 'if' key. Found another 'if' at position $i.");
}
}
return ExecuteConditionalActionAction(
initiator: initiator,
conditions: conditions);
}
factory ExecuteConditionalActionAction.from(dynamic payload) =>
ExecuteConditionalActionAction.fromYaml(payload: Utils.getYamlMap(payload));

Future<dynamic> _execute(Map actionMap,BuildContext context, ScopeManager scopeManager) async {
EnsembleAction? action = EnsembleAction.fromYaml(YamlMap.wrap(actionMap));
if ( action == null ) {
throw LanguageError(
"${ActionType.executeConditionalAction.name} requires a valid action.");
}
return ScreenController().executeActionWithScope(context, scopeManager, action);
}
@override
Future<dynamic> execute(BuildContext context, ScopeManager scopeManager) async {
for (var condition in conditions) {
String? conditionType;
var result = true; // Default to true for 'else'

if (condition.containsKey('if')) {
conditionType = 'if';
result = scopeManager.dataContext.eval(condition['if']);
} else if (condition.containsKey('elseif')) {
conditionType = 'elseif';
result = scopeManager.dataContext.eval(condition['elseif']);
} else if (condition.containsKey('else')) {
conditionType = 'else';
} else {
throw LanguageError(
"${ActionType.executeConditionalAction.name} requires a valid condition.");
}

if (result) {
Map? actionMap = condition['action'];
if (actionMap == null) {
throw LanguageError(
"${ActionType.executeConditionalAction.name} $conditionType condition requires a valid action.");
}
return _execute(actionMap, context, scopeManager);
}
}
return Future.value(
null); // No conditions met or all conditions evaluated to false.
}
}
//used to dispatch events. Used within custom widgets as custom widgets expose events to the callers
class DispatchEventAction extends EnsembleAction {
DispatchEventAction({super.initiator, required this.event, this.onComplete});
Expand Down Expand Up @@ -972,7 +1093,9 @@ enum ActionType {
createDeeplink,
verifySignIn,
signOut,
dispatchEvent
dispatchEvent,
executeConditionalAction,
executeActionGroup
}

enum ToastType { success, error, warning, info }
Expand Down Expand Up @@ -1139,6 +1262,10 @@ abstract class EnsembleAction {
} else if (actionType == ActionType.dispatchEvent) {
return DispatchEventAction.fromYaml(
initiator: initiator, payload: payload);
} else if ( actionType == ActionType.executeConditionalAction ) {
return ExecuteConditionalActionAction.fromYaml(initiator: initiator, payload: payload);
} else if ( actionType == ActionType.executeActionGroup ) {
return ExecuteActionGroupAction.fromYaml(initiator: initiator, payload: payload);
}

throw LanguageError("Invalid action.",
Expand Down
11 changes: 11 additions & 0 deletions lib/framework/data_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import 'package:jsparser/jsparser.dart';
/// This class can evaluate expressions based on the data scope
class DataContext {
final Map<String, dynamic> _contextMap = {};
get contextMap => _contextMap;
final BuildContext buildContext;

DataContext({required this.buildContext, Map<String, dynamic>? initialMap}) {
Expand Down Expand Up @@ -437,6 +438,16 @@ class NativeInvokable extends ActionInvokable {
buildContext,
DispatchEventAction.from(inputs),
),
ActionType.executeConditionalAction.name: (inputs) =>
ScreenController().executeAction(
buildContext,
ExecuteConditionalActionAction.from(inputs),
),
ActionType.executeActionGroup.name: (inputs) =>
ScreenController().executeAction(
buildContext,
ExecuteActionGroupAction.from(inputs),
),
});
return methods;
}
Expand Down
56 changes: 56 additions & 0 deletions lib/framework/devmode.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import 'package:ensemble/framework/data_context.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';

class DevMode {
static bool debug = false;
static String? screenId;
static String? screenName;
static DataContext? _pageDataContext;

static DataContext? get pageDataContext => _pageDataContext;

static set pageDataContext(DataContext? dataContext) {
if (!debug) return;
_pageDataContext = dataContext;
}

// Recursive function to process Invokable objects at any depth
static Map<String, dynamic> processInvokable(Invokable invokable) {
Map<String, dynamic> propMap = {};
List<String> getters = Invokable.getGettableProperties(invokable);
for (String getter in getters) {
dynamic propValue = invokable.getProperty(getter);

// Check if the property value is also an Invokable and process it recursively
if (propValue is Invokable) {
propValue = processInvokable(propValue);
}

propMap[getter] = propValue;
}
Map<String, Function> methods = Invokable.getMethods(invokable);
for (String method in methods.keys) {
Function f = methods[method]!;
propMap[method] = 'function() {}';
}

return propMap;
}

static Map<String, dynamic> getContextAsJs(Map<String, dynamic> contextMap) {
Map<String, dynamic> context = {};

contextMap.forEach((key, value) {
if (value is Invokable) {
// Use the recursive function to process Invokable objects deeply
Map<String, dynamic> propMap = processInvokable(value);

// Add propMap to context only if the value is Invokable
context[key] = propMap;
}
// If the value does not extend Invokable, it's not added to the context
});

return context;
}
}
3 changes: 2 additions & 1 deletion lib/framework/view/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:developer';

import 'package:ensemble/ensemble.dart';
import 'package:ensemble/framework/data_context.dart';
import 'package:ensemble/framework/devmode.dart';
import 'package:ensemble/framework/extensions.dart';
import 'package:ensemble/framework/menu.dart';
import 'package:ensemble/framework/model.dart';
Expand Down Expand Up @@ -448,7 +449,7 @@ class PageState extends State<Page>
: FloatingActionButtonLocation.endTop),
),
);

DevMode.pageDataContext = _scopeManager.dataContext;
// selectableText at the root
if (Utils.optionalBool(widget._pageModel.pageStyles?['selectable']) ==
true) {
Expand Down
7 changes: 7 additions & 0 deletions lib/layout/tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ abstract class BaseTabBar extends StatefulWidget
'id': (value) => _controller.id = Utils.optionalString(value),
'tabPosition': (position) =>
_controller.tabPosition = Utils.optionalString(position),
'tabAlignment': (alignment) =>
_controller.tabAlignment = Utils.optionalString(alignment),
'indicatorSize': (type) =>
_controller.indicatorSize = Utils.optionalString(type),
'margin': (margin) => _controller.margin = Utils.optionalInsets(margin),
Expand Down Expand Up @@ -95,6 +97,7 @@ abstract class BaseTabBar extends StatefulWidget

class TabBarController extends BoxController {
String? tabPosition;
String? tabAlignment;
String? indicatorSize;
String? tabType;
EdgeInsets? tabPadding;
Expand Down Expand Up @@ -285,6 +288,9 @@ class TabBarState extends WidgetState<BaseTabBar>

final indicatorSize =
TabBarIndicatorSize.values.from(widget._controller.indicatorSize);
final tabAlignment =
TabAlignment.values.from(widget._controller.tabAlignment) ??
TabAlignment.start;

Widget tabBar = TabBar(
labelPadding: labelPadding,
Expand All @@ -302,6 +308,7 @@ class TabBarState extends WidgetState<BaseTabBar>
),
controller: _tabController,
isScrollable: labelPosition,
tabAlignment: tabAlignment,
indicatorSize: indicatorSize,
labelStyle: tabStyle,
labelColor: widget._controller.activeTabColor ??
Expand Down
3 changes: 3 additions & 0 deletions lib/screen_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/bindings.dart';
import 'package:ensemble/framework/data_context.dart';
import 'package:ensemble/framework/device.dart';
import 'package:ensemble/framework/devmode.dart';
import 'package:ensemble/framework/error_handling.dart';
import 'package:ensemble/framework/event.dart';
import 'package:ensemble/framework/permissions_manager.dart';
Expand Down Expand Up @@ -682,6 +683,8 @@ class ScreenController {
Map<String, dynamic>? pageArgs,
required bool isExternal,
}) {
DevMode.screenId = screenId;
DevMode.screenName = screenName;
PageType pageType = asModal == true ? PageType.modal : PageType.regular;
return Screen(
key: key,
Expand Down
2 changes: 1 addition & 1 deletion lib/widget/Text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class EnsembleText extends StatefulWidget
@override
Map<String, Function> getters() {
return {
'text': () => _controller.text,
'text': () => _controller.text ?? '',
'textStyle': () => _controller.textStyle
};
}
Expand Down
2 changes: 1 addition & 1 deletion lib/widget/input/form_textfield.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ abstract class BaseTextInput extends StatefulWidget
@override
Map<String, Function> getters() {
return {
'value': () => textController.text,
'value': () => textController.text ?? ''
};
}

Expand Down

0 comments on commit de0d4e7

Please sign in to comment.