Skip to content

Commit

Permalink
Merge branch 'main' into feat/calendar
Browse files Browse the repository at this point in the history
  • Loading branch information
snehmehta committed Oct 19, 2023
2 parents 93db63b + b9c4f16 commit cbbfb65
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 84 deletions.
41 changes: 41 additions & 0 deletions assets/schema/ensemble_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,10 @@
"type": "boolean",
"description": "enable the toggling between plain and obscure text."
},
"enableClearText": {
"type": "boolean",
"description": "It enables the default suffix clear icon button for the text input field to clear the values. Default (false)"
},
"onDelayedKeyPress": {
"$ref": "#/$defs/Action-payload",
"description": "Execute an Action after a brief delay specified by delayedKeyPressDuration (default: 300 ms). Consecutive calls within this delay will reset the timer, useful for minimizing server calls for type-ahead scenarios."
Expand Down Expand Up @@ -2861,6 +2865,13 @@
"focusedErrorBorderColor": {
"$ref": "#/$defs/typeColors",
"description": "The border color of this input field when it is receiving focus in its error state. This property can be defined in the theme to apply to all Input widgets."
},
"floatLabel": {
"type": "boolean",
"description": "Moves the label on top of the Input Field. Default (False)."
},
"labelStyle": {
"$ref": "#/$defs/TextStyle"
}
}
}
Expand Down Expand Up @@ -2970,6 +2981,36 @@
}
}
},
{
"title": "Navigate External Screen",
"type": "object",
"required": [
"navigateExternalScreen"
],
"properties": {
"navigateExternalScreen": {
"type": "object",
"description": "Navigating to a native screen outside of Ensemble (only iOS screens are currently supported - additional integration is required)",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "The name of the screen."
},
"inputs": {
"type": "object",
"description": "Specify the key/value pairs to pass into the Screen",
"properties": {},
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
{
"title": "Navigate Modal Screen",
"type": "object",
Expand Down
30 changes: 30 additions & 0 deletions ensemble/integration_tests/defaultApp/Conditional.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
View:
title: Conditional

body:
Column:
styles: { gap: 16, padding: 24 }
children:
- TextInput:
id: textInputId
onDelayedKeyPress: |
//@code
textInputId.value = textInputId.value;
- Conditional:
conditions:
- if: ${textInputId.value == 'If'}
Text:
text: If Statement
- elseif: ${textInputId.value == 'ElseIf1'}
Text:
text: Else If Statement - 1
- elseif: ${textInputId.value == 'ElseIf2'}
Text:
text: Else If Statement - 2
- elseif: ${textInputId.value == 'ElseIf3'}
Text:
text: Else If Statement - 3
- else:
Text:
text: Else Statement
50 changes: 50 additions & 0 deletions integration_test/defaultApp_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:io';

import 'package:ensemble/ensemble.dart';
import 'package:ensemble/widget/button.dart';
import 'package:ensemble/widget/conditional.dart';
import 'package:ensemble/widget/input/dropdown.dart';
import 'package:ensemble/widget/input/form_textfield.dart';
import 'package:ensemble/widget/text.dart';
Expand Down Expand Up @@ -206,5 +207,54 @@ void main() {
// verified value 'six' is selected
expect(find.text('six'), findsOneWidget);
});

testWidgets('Conditional', (tester) async {
await TestHelper.loadScreen(screenName: 'Conditional', config: config);
await tester.pumpAndSettle();

Finder textInputFinder = find.byType(TextInput);
expect(textInputFinder, findsOneWidget);

// one Conditional widget on the screen
Finder conditionalFinder = find.byType(Conditional);
expect(conditionalFinder, findsOneWidget);

Finder textFinder = find.byType(EnsembleText);
expect(textFinder, findsOneWidget);

// Initial Statement when textfield is empty
EnsembleText textWidget = tester.firstWidget(textFinder);
expect(textWidget.controller.text, 'Else Statement');

// If Statement
await tester.enterText(textInputFinder, 'If');
await tester.pumpAndSettle();
textWidget = tester.widget(textFinder);
expect(textWidget.controller.text, 'If Statement');

// Else If First Statement
await tester.enterText(textInputFinder, 'ElseIf1');
await tester.pumpAndSettle();
textWidget = tester.widget(textFinder);
expect(textWidget.controller.text, 'Else If Statement - 1');

// Else If Second Statement
await tester.enterText(textInputFinder, 'ElseIf2');
await tester.pumpAndSettle();
textWidget = tester.widget(textFinder);
expect(textWidget.controller.text, 'Else If Statement - 2');

// Else If Third Statement
await tester.enterText(textInputFinder, 'ElseIf3');
await tester.pumpAndSettle();
textWidget = tester.widget(textFinder);
expect(textWidget.controller.text, 'Else If Statement - 3');

// Else Statement
await tester.enterText(textInputFinder, 'Other');
await tester.pumpAndSettle();
textWidget = tester.widget(textFinder);
expect(textWidget.controller.text, 'Else Statement');
});
});
}
10 changes: 5 additions & 5 deletions lib/action/call_external_method.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class CallExternalMethod extends EnsembleAction {
}

@override
Future<void> execute(BuildContext context, ScopeManager scopeManager) {
Future<void> execute(BuildContext context, ScopeManager scopeManager) async {
String? name = Utils.optionalString(scopeManager.dataContext.eval(_name));

String? errorReason;
Expand All @@ -46,16 +46,16 @@ class CallExternalMethod extends EnsembleAction {
_payload?.forEach((key, value) {
(payload ??= {})[Symbol(key)] = scopeManager.dataContext.eval(value);
});
// execute the external function
dynamic rtnValue =
Function.apply(Ensemble().externalMethods[name]!, null, payload);
// execute the external function. Always await in case it's async
dynamic rtnValue = await Function.apply(
Ensemble().externalMethods[name]!, null, payload);

// dispatch onComplete
if (onComplete != null) {
ScreenController().executeAction(context, onComplete!,
event: EnsembleEvent(null, data: rtnValue));
}
return Future.value(null);
return rtnValue;
} catch (e) {
errorReason = e.toString();
}
Expand Down
46 changes: 46 additions & 0 deletions lib/action/navigation_action.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,53 @@
import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/error_handling.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/host_platform_manager.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/cupertino.dart';

/// Navigate to a screen outside of Ensemble (i.e. Flutter, iOS or Android screen)
class NavigateExternalScreen extends BaseNavigateScreenAction {
NavigateExternalScreen(
{super.initiator, required super.screenName, super.inputs, super.options})
: super(asModal: false, isExternal: false);

factory NavigateExternalScreen.from({Invokable? initiator, Map? payload}) {
if (payload?['name'] == null) {
throw LanguageError(
"${ActionType.navigateExternalScreen.name} requires a 'name' of the screen");
}
return NavigateExternalScreen(
initiator: initiator,
screenName: payload!['name'].toString(),
inputs: Utils.getMap(payload['inputs']),
options: Utils.getMap(payload['options']));
}

@override
Future<void> execute(BuildContext context, ScopeManager scopeManager) {
// payload
Map<String, dynamic>? payload;
if (inputs != null) {
payload = {};
inputs!.forEach(
(key, value) => payload![key] = scopeManager.dataContext.eval(value));
}
// options
Map<String, dynamic>? screenOptions;
if (options != null) {
screenOptions = {};
options!.forEach((key, value) =>
screenOptions![key] = scopeManager.dataContext.eval(value));
}
return HostPlatformManager().navigateExternalScreen({
'name': scopeManager.dataContext.eval(screenName),
'inputs': payload,
'options': screenOptions
});
}
}

/// pop the current screen, but we abstract it as a navigate back
class NavigateBackAction extends EnsembleAction {
NavigateBackAction({this.payload});
Expand Down
4 changes: 3 additions & 1 deletion lib/deep_link_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class DeepLinkNavigator {
?.toString();
if (screenId != null || screenName != null) {
ScreenController().navigateToScreen(Utils.globalAppKey.currentContext!,
screenId: screenId, screenName: screenName);
screenId: screenId,
screenName: screenName,
pageArgs: uri.queryParameters);
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions lib/ensemble.dart
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,14 @@ class EnsembleConfig {
return this;
}

/// update Env Variables at any later time wil override the values we
/// got from our config file
void updateEnvOverrides(Map<String, dynamic> updatedMap) {
if (updatedMap.isNotEmpty) {
(envOverrides ??= {}).addAll(updatedMap);
}
}

/// pass our custom theme from the appBundle and build the App Theme
ThemeData getAppTheme() {
return ThemeManager().getAppTheme(appBundle?.theme);
Expand Down
4 changes: 4 additions & 0 deletions lib/framework/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@ class CheckPermission extends EnsembleAction {
enum ActionType {
invokeAPI,
navigateScreen,
navigateExternalScreen,
navigateModalScreen,
showBottomModal,
dismissBottomModal,
Expand Down Expand Up @@ -915,6 +916,9 @@ abstract class EnsembleAction {
if (actionType == ActionType.navigateScreen) {
return NavigateScreenAction.fromYaml(
initiator: initiator, payload: payload);
} else if (actionType == ActionType.navigateExternalScreen) {
return NavigateExternalScreen.from(
initiator: initiator, payload: payload);
} else if (actionType == ActionType.navigateModalScreen) {
return NavigateModalScreenAction.fromYaml(
initiator: initiator, payload: payload);
Expand Down
80 changes: 80 additions & 0 deletions lib/host_platform_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:ensemble/ensemble.dart';
import 'package:ensemble/framework/extensions.dart';
import 'package:flutter/services.dart';

/// managing communication between Ensemble and the host platform (iOS/Android)
class HostPlatformManager extends IsHostPlatformManager
with _InboundManager, _OutboundManager {
HostPlatformManager._internal();

static final HostPlatformManager _instance = HostPlatformManager._internal();

factory HostPlatformManager() => _instance;

static const MethodChannel _channel =
MethodChannel('com.ensembleui.host.platform');

@override
MethodChannel getChannel() => _channel;

void init() {
_channel.setMethodCallHandler(_receiveData);
}
}

abstract class IsHostPlatformManager {
MethodChannel getChannel();
}

/// Handle Inbound methods
mixin _InboundManager on IsHostPlatformManager {
Future<void> _receiveData(MethodCall call) async {
Method? method = Method.values.from(call.method);
switch (method) {
case Method.updateEnvOverrides:
_updateEnvOverrides(call.arguments);
break;
case Method.fromHostToEnsemble:
print("Data From HOST: ${call.arguments}");
break;
default:
break;
}
}

/// data coming from hosting platform's envVariable channel
/// will be added to the environment overrides
void _updateEnvOverrides(dynamic payload) {
if (payload is Map && payload.isNotEmpty) {
Map<String, dynamic> variables = {};
payload.forEach((key, value) {
variables[key.toString()] = value;
});
Ensemble().getConfig()?.updateEnvOverrides(variables);
}
}
}

/// Handle Outbound methods
mixin _OutboundManager on IsHostPlatformManager {
Future<void> navigateExternalScreen(dynamic payload) async {
return await getChannel()
.invokeMethod(Method.navigateExternalScreen.name, payload);
}

void sendData(dynamic payload) {
getChannel().invokeMethod(Method.fromEnsembleToHost.name, payload);
}
}

enum Method {
// for sending environment variables from host to Ensemble
updateEnvOverrides,

// navigate to a host platform's screen
navigateExternalScreen,

// twp-way communication between host and Ensemble
fromHostToEnsemble,
fromEnsembleToHost
}
2 changes: 2 additions & 0 deletions lib/screen_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class ScreenController {
if (action is InvokeAPIAction) {
await InvokeAPIController()
.execute(action, context, dataContext, scopeManager, apiMap);
} else if (action is NavigateExternalScreen) {
return action.execute(context, scopeManager!);
} else if (action is BaseNavigateScreenAction) {
// process input parameters
Map<String, dynamic>? nextArgs = {};
Expand Down
2 changes: 1 addition & 1 deletion lib/widget/conditional.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class ConditionalState extends WidgetState<Conditional> {
return _buildWidget(scopeManager, conditions.first);
}

for (var i = 1; i < conditions.length - 1; i++) {
for (var i = 1; i <= conditions.length - 1; i++) {
final condition = conditions[i];

if (condition.containsKey('elseif') &&
Expand Down
Loading

0 comments on commit cbbfb65

Please sign in to comment.