Skip to content

[WIP] Fix Invokable actions [JS Compatibility] #1964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions modules/ensemble/lib/action/action_invokable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ abstract class ActionInvokable with Invokable {
ActionType.saveFile,
ActionType.controlDeviceBackNavigation,
ActionType.closeApp,
ActionType.getLocation,
ActionType.pickFiles,
ActionType.openPlaidLink,
ActionType.updateBadgeCount,
ActionType.clearBadgeCount,
]);
}

Expand Down
72 changes: 72 additions & 0 deletions modules/ensemble/lib/action/file_picker_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/error_handling.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/framework/stub/file_manager.dart';
import 'package:ensemble/screen_controller.dart';
import 'package:ensemble/util/utils.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:yaml/yaml.dart';
import 'package:ensemble/framework/event.dart';
import 'package:flutter/cupertino.dart';

enum FileSource { gallery, files }

class FilePickerAction extends EnsembleAction {
FilePickerAction({
required this.id,
this.allowedExtensions,
this.allowMultiple,
this.allowCompression,
this.onComplete,
this.onError,
this.source,
});

String id;
List<String>? allowedExtensions;
bool? allowMultiple;
bool? allowCompression;
EnsembleAction? onComplete;
EnsembleAction? onError;
FileSource? source;

factory FilePickerAction.fromYaml({Map? payload}) {
if (payload == null || payload['id'] == null) {
throw LanguageError("${ActionType.pickFiles.name} requires 'id'.");
}

FileSource? getSource(String? source) {
if (source == 'gallery') {
return FileSource.gallery;
}
if (source == 'files') {
return FileSource.files;
}
return null;
}

return FilePickerAction(
id: Utils.getString(payload['id'], fallback: ''),
allowedExtensions:
(payload['allowedExtensions'] as YamlList?)?.cast<String>().toList(),
allowMultiple: Utils.optionalBool(payload['allowMultiple']),
allowCompression: Utils.optionalBool(payload['allowCompression']),
onComplete: EnsembleAction.from(payload['onComplete']),
onError: EnsembleAction.from(payload['onError']),
source: getSource(payload['source']),
);
}

@override
Future execute(BuildContext context, ScopeManager scopeManager) async {
try {
await GetIt.I<FileManager>().pickFiles(context, this, scopeManager);
} catch (e) {
if (onError != null) {
await ScreenController().executeAction(context, onError!,
event: EnsembleEvent(null, error: e.toString()));
}
}
}
}
102 changes: 102 additions & 0 deletions modules/ensemble/lib/action/getLocation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'dart:async';
import 'package:ensemble/framework/action.dart';
import 'package:ensemble/framework/device.dart';
import 'package:ensemble/framework/error_handling.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/framework/stub/location_manager.dart';
import 'package:ensemble/screen_controller.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';

class GetLocationAction extends EnsembleAction {
GetLocationAction({
super.initiator,
this.onLocationReceived,
this.onError,
this.recurring,
this.recurringDistanceFilter,
});

final EnsembleAction? onLocationReceived;
final EnsembleAction? onError;
final bool? recurring;
final int? recurringDistanceFilter;

factory GetLocationAction.fromYaml({Invokable? initiator, Map? payload}) {
return GetLocationAction(
initiator: initiator,
onLocationReceived: EnsembleAction.from(payload?['onLocationReceived']),
onError: EnsembleAction.from(payload?['onError']),
recurring: Utils.optionalBool(payload?['options']?['recurring']),
recurringDistanceFilter: Utils.optionalInt(
payload?['options']?['recurringDistanceFilter'],
min: 50),
);
}

@override
Future<void> execute(BuildContext context, ScopeManager scopeManager) async {
if (onLocationReceived == null) {
throw LanguageError(
'${ActionType.getLocation.name} requires onLocationReceived callback');
}

try {
final status = await GetIt.I<LocationManager>().getLocationStatus();

if (status == LocationStatus.ready) {
// Handle recurring location updates
if (recurring == true) {
StreamSubscription<LocationData> streamSubscription =
GetIt.I<LocationManager>()
.getPositionStream(
distanceFilter: recurringDistanceFilter ?? 1000)
.map((position) => LocationData(
latitude: position.latitude,
longitude: position.longitude))
.listen((LocationData? location) {
if (location != null) {
// Update last known location
Device().updateLastLocation(location);

// Add location data to context and execute callback
scopeManager.dataContext
.addDataContextById('latitude', location.latitude);
scopeManager.dataContext
.addDataContextById('longitude', location.longitude);
ScreenController().executeAction(context, onLocationReceived!);
} else if (onError != null) {
scopeManager.dataContext.addDataContextById('reason', 'unknown');
ScreenController().executeAction(context, onError!);
}
});
scopeManager.addLocationListener(streamSubscription);
}
// Handle one-time location request
else {
final location = await GetIt.I<LocationManager>().simplyGetLocation();
if (location != null) {
Device().updateLastLocation(location);
scopeManager.dataContext
.addDataContextById('latitude', location.latitude);
scopeManager.dataContext
.addDataContextById('longitude', location.longitude);
ScreenController().executeAction(context, onLocationReceived!);
}
}
} else if (onError != null) {
scopeManager.dataContext.addDataContextById('reason', status.name);
ScreenController().executeAction(context, onError!);
}
} catch (e) {
if (onError != null) {
scopeManager.dataContext.addDataContextById('reason', e.toString());
ScreenController().executeAction(context, onError!);
} else {
rethrow;
}
}
}
}
132 changes: 61 additions & 71 deletions modules/ensemble/lib/framework/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:ensemble/action/device_security.dart';
import 'package:ensemble/action/dialog_actions.dart';
import 'package:ensemble/action/drawer_actions.dart';
import 'package:ensemble/action/execute_action_group_action.dart';
import 'package:ensemble/action/file_picker_action.dart';
import 'package:ensemble/action/get_network_info_action.dart';
import 'package:ensemble/action/haptic_action.dart';
import 'package:ensemble/action/call_native_method.dart';
Expand All @@ -27,6 +28,7 @@ import 'package:ensemble/action/toast_actions.dart';
import 'package:ensemble/action/take_screenshot.dart';
import 'package:ensemble/action/disable_hardware_navigation.dart';
import 'package:ensemble/action/close_app.dart';
import 'package:ensemble/action/getLocation.dart';
import 'package:ensemble/ensemble.dart';
import 'package:ensemble/framework/data_context.dart';
import 'package:ensemble/framework/error_handling.dart';
Expand All @@ -35,6 +37,7 @@ import 'package:ensemble/framework/extensions.dart';
import 'package:ensemble/framework/keychain_manager.dart';
import 'package:ensemble/framework/permissions_manager.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:ensemble/framework/stub/plaid_link_manager.dart';
import 'package:ensemble/framework/view/page_group.dart';
import 'package:ensemble/framework/widget/view_util.dart';
import 'package:ensemble/receive_intent_manager.dart';
Expand All @@ -46,6 +49,7 @@ import 'package:ensemble_ts_interpreter/invokables/invokable.dart';
import 'package:flutter/material.dart';
import 'package:source_span/source_span.dart';
import 'package:yaml/yaml.dart';
import 'package:get_it/get_it.dart';

class ShowCameraAction extends EnsembleAction {
ShowCameraAction({
Expand Down Expand Up @@ -254,6 +258,62 @@ class PlaidLinkAction extends EnsembleAction {
onExit: EnsembleAction.from(payload['onExit']),
);
}

@override
Future execute(BuildContext context, ScopeManager scopeManager) async {
final linkToken = getLinkToken(scopeManager.dataContext).trim();
if (linkToken.isNotEmpty) {
try {
GetIt.I<PlaidLinkManager>().openPlaidLink(
linkToken,
(linkSuccess) {
if (onSuccess != null) {
ScreenController().executeActionWithScope(
context,
scopeManager,
onSuccess!,
event: EnsembleEvent(
initiator,
data: linkSuccess,
),
);
}
},
(linkEvent) {
if (onEvent != null) {
ScreenController().executeActionWithScope(
context,
scopeManager,
onEvent!,
event: EnsembleEvent(
initiator,
data: linkEvent,
),
);
}
},
(linkExit) {
if (onExit != null) {
ScreenController().executeActionWithScope(
context,
scopeManager,
onExit!,
event: EnsembleEvent(
initiator,
data: linkExit,
),
);
}
},
);
} catch (e) {
throw ConfigError("Error opening Plaid link: $e");
}
} else {
throw RuntimeError(
"openPlaidLink action requires the plaid's link_token.");
}
}
}

class ReceiveIntentAction extends EnsembleAction {
Expand Down Expand Up @@ -571,69 +631,6 @@ class OpenUrlAction extends EnsembleAction {
OpenUrlAction.fromYaml(payload: Utils.getYamlMap(inputs));
}

class GetLocationAction extends EnsembleAction {
GetLocationAction(
{this.onLocationReceived,
this.onError,
this.recurring,
this.recurringDistanceFilter});

EnsembleAction? onLocationReceived;
EnsembleAction? onError;

bool? recurring;
int? recurringDistanceFilter;
}

enum FileSource { gallery, files }

class FilePickerAction extends EnsembleAction {
FilePickerAction({
required this.id,
this.allowedExtensions,
this.allowMultiple,
this.allowCompression,
this.onComplete,
this.onError,
this.source,
});

String id;
List<String>? allowedExtensions;
bool? allowMultiple;
bool? allowCompression;
EnsembleAction? onComplete;
EnsembleAction? onError;
FileSource? source;

factory FilePickerAction.fromYaml({Map? payload}) {
if (payload == null || payload['id'] == null) {
throw LanguageError("${ActionType.pickFiles.name} requires 'id'.");
}

FileSource? getSource(String? source) {
if (source == 'gallery') {
return FileSource.gallery;
}
if (source == 'files') {
return FileSource.files;
}
return null;
}

return FilePickerAction(
id: Utils.getString(payload['id'], fallback: ''),
allowedExtensions:
(payload['allowedExtensions'] as YamlList?)?.cast<String>().toList(),
allowMultiple: Utils.optionalBool(payload['allowMultiple']),
allowCompression: Utils.optionalBool(payload['allowCompression']),
onComplete: EnsembleAction.from(payload['onComplete']),
onError: EnsembleAction.from(payload['onError']),
source: getSource(payload['source']),
);
}
}

class FileUploadAction extends EnsembleAction {
FileUploadAction({
super.inputs,
Expand Down Expand Up @@ -1182,14 +1179,7 @@ abstract class EnsembleAction {
} else if (actionType == ActionType.executeCode) {
return ExecuteCodeAction.fromYaml(initiator: initiator, payload: payload);
} else if (actionType == ActionType.getLocation) {
return GetLocationAction(
onLocationReceived:
EnsembleAction.from(payload?['onLocationReceived']),
onError: EnsembleAction.from(payload?['onError']),
recurring: Utils.optionalBool(payload?['options']?['recurring']),
recurringDistanceFilter: Utils.optionalInt(
payload?['options']?['recurringDistanceFilter'],
min: 50));
return GetLocationAction.fromYaml(initiator: initiator, payload: payload);
} else if (actionType == ActionType.pickFiles) {
return FilePickerAction.fromYaml(payload: payload);
} else if (actionType == ActionType.uploadFiles) {
Expand Down
2 changes: 1 addition & 1 deletion modules/ensemble/lib/framework/stub/file_manager.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:typed_data';

import 'package:ensemble/framework/action.dart';
import 'package:ensemble/action/file_picker_action.dart';
import 'package:ensemble/framework/error_handling.dart';
import 'package:ensemble/framework/scope.dart';
import 'package:flutter/cupertino.dart';
Expand Down
Loading