Merge branch 'multilng-about-json' of github.com:AnonymusRaccoon/Aeris into multilng-about-json

This commit is contained in:
Clément Le Bihan
2022-03-04 18:13:32 +01:00
9 changed files with 367 additions and 300 deletions
+4 -8
View File
@@ -1,15 +1,16 @@
import 'package:aeris/src/models/action_parameter.dart';
import 'package:flutter/widgets.dart';
import 'package:aeris/src/models/service.dart';
import 'package:recase/recase.dart';
///Base class for reactions and trigger
abstract class Action {
///Action's service
Service service;
///Name fo the action
///Identifier of the action type
String name;
///Name odf the action
String displayName;
///Action's parameters
List<ActionParameter> parameters;
@@ -21,6 +22,7 @@ abstract class Action {
{Key? key,
required this.service,
required this.name,
required this.displayName,
this.description,
this.parameters = const []});
@@ -29,10 +31,4 @@ abstract class Action {
var service = snake.removeAt(0);
return Service.factory(service);
}
String displayName() {
var words = name.split('_');
words.removeAt(0);
return ReCase(words.join()).titleCase;
}
}
+2 -1
View File
@@ -13,8 +13,9 @@ class ActionTemplate extends Action {
{Key? key,
required Service service,
required String name,
required String displayName,
required String description,
this.returnedValues = const [],
List<ActionParameter> parameters = const []})
: super(service: service, name: name, parameters: parameters, description: description);
: super(service: service, name: name, parameters: parameters, description: description, displayName: displayName);
}
+14 -4
View File
@@ -1,9 +1,12 @@
// ignore_for_file: hash_and_equals
import 'package:aeris/main.dart';
import 'package:aeris/src/models/action.dart' as aeris_action;
import 'package:aeris/src/models/action_parameter.dart';
import 'package:aeris/src/providers/action_catalogue_provider.dart';
import 'package:flutter/widgets.dart';
import 'package:aeris/src/models/service.dart';
import 'package:provider/provider.dart';
///Object representation of a reaction
class Reaction extends aeris_action.Action {
@@ -11,19 +14,26 @@ class Reaction extends aeris_action.Action {
{Key? key,
required Service service,
required String name,
required String displayName,
List<ActionParameter> parameters = const []})
: super(service: service, name: name, parameters: parameters);
: super(service: service, name: name, parameters: parameters, displayName: displayName);
/// Template trigger, used as an 'empty' trigger
Reaction.template()
: super(service: Service.all()[0], name: '', parameters: []);
: super(service: Service.all()[0], name: '', parameters: [],displayName: '');
static Reaction fromJSON(Object reaction) {
var reactionJSON = reaction as Map<String, dynamic>;
var service = aeris_action.Action.parseServiceInName(reactionJSON['rType'] as String);
String rType = reactionJSON['rType'] as String;
var service = aeris_action.Action.parseServiceInName(rType);
return Reaction(
displayName: reactionJSON['label']?['en']
?? Provider.of<ActionCatalogueProvider>(Aeris.materialKey.currentContext!, listen: false)
.reactionTemplates[service]!.firstWhere((template) {
return template.name == rType;
}).displayName, ///TODO use locale
service: service,
name: reactionJSON['rType'] as String,
name: rType,
parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map<String, dynamic>)));
}
+12 -3
View File
@@ -1,11 +1,13 @@
// ignore_for_file: hash_and_equals
import 'package:aeris/src/models/action_parameter.dart';
import 'package:aeris/src/providers/action_catalogue_provider.dart';
import 'package:flutter/material.dart';
import 'package:aeris/main.dart';
import 'package:aeris/src/models/service.dart';
import 'package:aeris/src/models/action.dart' as aeris_action;
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
///Object representation of a pipeline trigger
class Trigger extends aeris_action.Action {
@@ -15,9 +17,10 @@ class Trigger extends aeris_action.Action {
{Key? key,
required Service service,
required String name,
required String displayName,
List<ActionParameter> parameters = const [],
this.last})
: super(service: service, name: name, parameters: parameters);
: super(service: service, name: name, parameters: parameters, displayName: displayName);
/// Unserialize
static Trigger fromJSON(Object action) {
@@ -28,10 +31,16 @@ class Trigger extends aeris_action.Action {
DateTime? last = lastTriggerField == null
? null
: DateTime.parse(lastTriggerField as String);
String pType = triggerJSON['pType'] as String;
return Trigger(
displayName: triggerJSON['label']?['en']
?? Provider.of<ActionCatalogueProvider>(Aeris.materialKey.currentContext!, listen: false)
.triggerTemplates[service]!.firstWhere((template) {
return template.name == pType;
}).displayName, ///TODO use locale
service: service,
name: triggerJSON['pType'] as String,
name: pType,
last: last,
parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map<String, dynamic>))
);
@@ -49,7 +58,7 @@ class Trigger extends aeris_action.Action {
/// Template trigger, used as an 'empty' trigger
Trigger.template({Key? key, this.last})
: super(service: Service.all()[0], name: '', parameters: []);
: super(service: Service.all()[0], name: '', parameters: [], displayName: '');
@override
// ignore: avoid_renaming_method_parameters
@@ -36,13 +36,14 @@ class ActionCatalogueProvider extends ChangeNotifier {
_triggerTemplates[service]!.add(
ActionTemplate(
name: action['name'],
displayName: action['label']['en'], ///TODO use locale
service: service,
description: action['description'],
description: action['description']['en'], ///TODO use locale
parameters: (action['params'] as List).map(
(e) => ActionParameter(name: e['name'], description: e['description'])
(e) => ActionParameter(name: e['name'], description: e['description']['en'])///TODO use locale
).toList(),
returnedValues: (action['returns'] as List).map(
(e) => ActionParameter(name: e['name'], description: e['description'])
(e) => ActionParameter(name: e['name'], description: e['description']['en']) ///TODO use locale
).toList(),
)
);
@@ -50,14 +51,15 @@ class ActionCatalogueProvider extends ChangeNotifier {
for (var reaction in serviceContent['reactions']) {
_reactionTemplates[service]!.add(
ActionTemplate(
displayName: reaction['label']['en'], ///TODO use locale
name: reaction['name'],
service: service,
description: reaction['description'],
description: reaction['description']['en'], ///TODO use locale
parameters: (reaction['params'] as List).map(
(e) => ActionParameter(name: e['name'], description: e['description'])
(e) => ActionParameter(name: e['name'], description: e['description']['en']) ///TODO use locale
).toList(),
returnedValues: (reaction['returns'] as List).map(
(e) => ActionParameter(name: e['name'], description: e['description'])
(e) => ActionParameter(name: e['name'], description: e['description']['en']) ///TODO use locale
).toList(),
)
);
+160 -139
View File
@@ -38,149 +38,170 @@ class _CreatePipelinePageState extends State<CreatePipelinePage> {
final _formKey = GlobalKey<FormBuilderState>();
return Consumer<PipelineProvider>(
builder: (context, provider, _) => AerisCardPage(
body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(AppLocalizations.of(context).createNewPipeline,
style: const TextStyle(
fontSize: 25,
)),
FormBuilder(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
FormBuilderTextField(
name: 'name',
initialValue: name,
decoration: InputDecoration(
labelText:
AppLocalizations.of(context).nameOfThePipeline,
),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
FormBuilderValidators.minLength(context, 5),
]),
onChanged: (value) {
name = value;
},
),
const SizedBox(height: 10),
trigger != Trigger.template()
? Text(AppLocalizations.of(context).action,
style: const TextStyle(fontWeight: FontWeight.w500))
: Container(),
Padding(
padding: const EdgeInsets.all(8.0),
child: trigger == Trigger.template()
? ColoredClickableCard(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
text: AppLocalizations.of(context).addTrigger,
onTap: () {
showAerisCardPage(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context).createNewPipeline,
style: const TextStyle(
fontSize: 25,
)),
FormBuilder(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FormBuilderTextField(
name: 'name',
initialValue: name,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)
.nameOfThePipeline,
),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
FormBuilderValidators.minLength(context, 5),
]),
onChanged: (value) {
name = value;
},
),
const SizedBox(height: 10),
trigger != Trigger.template()
? Text(AppLocalizations.of(context).action,
style: const TextStyle(
fontWeight: FontWeight.w500))
: Container(),
Padding(
padding: const EdgeInsets.all(8.0),
child: trigger == Trigger.template()
? ColoredClickableCard(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
text: AppLocalizations.of(context)
.addTrigger,
onTap: () {
showAerisCardPage(
context,
(_) => SetupActionPage(
action: trigger,
parentReactions:
reactions))
.then((_) => setState(() {}));
})
: ActionCard(
leading: trigger.service
.getLogo(logoSize: 50),
title: trigger.displayName,
trailing: ActionCardPopupMenu(
deletable: false,
parentReactions: reactions,
parentTrigger: trigger,
action: trigger,
then: () => setState(() {})),
),
),
reactions.isNotEmpty
? Text(AppLocalizations.of(context).reactions,
style: const TextStyle(
fontWeight: FontWeight.w500))
: Container(),
Padding(
padding:
const EdgeInsets.only(left: 8, right: 8),
child: ReorderableReactionCardsList(
reactionList: reactions,
onReorder: () {},
itemBuilder: (reaction) => ActionCard(
key: ValueKey(
reactions.indexOf(reaction)),
leading: reaction.service
.getLogo(logoSize: 50),
title: reaction.displayName,
trailing: ActionCardPopupMenu(
parentTrigger:
trigger == Trigger.template()
? null
: trigger,
parentReactions: reactions,
deletable: reactions.length > 1,
action: reaction,
then: () => setState(() {}),
onDelete: () {
setState(() {
reactions.remove(reaction);
});
})),
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: ColoredClickableCard(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
text: AppLocalizations.of(context)
.addReaction,
onTap: () async {
var newreact = Reaction.template();
showAerisCardPage(
context,
(_) => SetupActionPage(
action: trigger,
parentReactions: reactions
))
.then((_) => setState(() {}));
})
: ActionCard(
leading: trigger.service.getLogo(logoSize: 50),
title: trigger.displayName(),
trailing: ActionCardPopupMenu(
deletable: false,
parentReactions: reactions,
parentTrigger: trigger,
action: trigger,
then: () => setState(() {})),
action: newreact,
parentReactions: reactions,
parentTrigger: trigger ==
Trigger.template()
? null
: trigger,
)).then((_) => setState(() {
if (newreact !=
Reaction.template()) {
reactions.add(newreact);
}
}));
}),
),
),
reactions.isNotEmpty
? Text(AppLocalizations.of(context).reactions,
style: const TextStyle(fontWeight: FontWeight.w500))
: Container(),
Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: ReorderableReactionCardsList(
reactionList: reactions,
onReorder: () { },
itemBuilder: (reaction) => ActionCard(
key: ValueKey(reactions.indexOf(reaction)),
leading: reaction.service.getLogo(logoSize: 50),
title: reaction.displayName(),
trailing: ActionCardPopupMenu(
parentTrigger: trigger == Trigger.template() ? null : trigger,
parentReactions: reactions,
deletable: reactions.length > 1,
action: reaction,
then: () => setState(() {}),
onDelete: () {
setState(() {
reactions.remove(reaction);
});
})),
)
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ColoredClickableCard(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
text: AppLocalizations.of(context).addReaction,
onTap: () async {
var newreact = Reaction.template();
showAerisCardPage(
context,
(_) => SetupActionPage(
action: newreact,
parentReactions: reactions,
parentTrigger: trigger == Trigger.template() ? null : trigger,
))
.then((_) => setState(() {
if (newreact != Reaction.template()) {
reactions.add(newreact);
Center(
child: ElevatedButton(
child: Text(AppLocalizations.of(context).save),
onPressed: () {
_formKey.currentState!.save();
if (_formKey.currentState!.validate()) {
if (trigger == Trigger.template() ||
reactions.isEmpty ||
reactions
.where((element) =>
element == Reaction.template())
.isNotEmpty) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
backgroundColor: Theme.of(context)
.colorScheme
.secondary,
content: const Text(
"You must select at least a trigger and a reaction")));
} else {
Pipeline newPipeline = Pipeline(
id: 0,
name: _formKey
.currentState!.value['name'],
triggerCount: 0,
enabled: true,
trigger: trigger,
reactions: reactions);
provider.addPipeline(newPipeline);
Navigator.of(context).pop();
showAerisCardPage(
context,
(_) => PipelineDetailPage(
pipeline: newPipeline));
}
}));
}),
),
Center(child: ElevatedButton(
child: Text(AppLocalizations.of(context).save),
onPressed: () {
_formKey.currentState!.save();
if (_formKey.currentState!.validate()) {
if (trigger == Trigger.template() ||
reactions.isEmpty ||
reactions
.where((element) =>
element == Reaction.template())
.isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
backgroundColor:
Theme.of(context).colorScheme.secondary,
content: const Text(
"You must select at least a trigger and a reaction")));
} else {
Pipeline newPipeline = Pipeline(
id: 0,
name: _formKey.currentState!.value['name'],
triggerCount: 0,
enabled: true,
trigger: trigger,
reactions: reactions);
provider.addPipeline(newPipeline);
Navigator.of(context).pop();
showAerisCardPage(
context,
(_) => PipelineDetailPage(pipeline: newPipeline)
);
}
}
},
}
},
)),
]),
)),
]),
)),
])));
])));
}
}
+9 -11
View File
@@ -95,13 +95,12 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
onTap: () {
Reaction newreaction = Reaction.template();
showAerisCardPage(
context, (_) => SetupActionPage(
context,
(_) => SetupActionPage(
action: newreaction,
parentTrigger: pipeline.trigger,
parentReactions: pipeline.reactions,
)
)
.then((r) {
)).then((r) {
if (newreaction != Reaction.template()) {
setState(() {
pipeline.reactions.add(newreaction);
@@ -140,7 +139,7 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
style: const TextStyle(fontWeight: FontWeight.w500)),
ActionCard(
leading: pipeline.trigger.service.getLogo(logoSize: 50),
title: pipeline.trigger.displayName(),
title: pipeline.trigger.displayName,
trailing: ActionCardPopupMenu(
deletable: false,
parentTrigger: pipeline.trigger,
@@ -154,12 +153,12 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
Text(AppLocalizations.of(context).reactions,
style: const TextStyle(fontWeight: FontWeight.w500)),
ReorderableReactionCardsList(
onReorder: () => GetIt.I<AerisAPI>().editPipeline(pipeline),
reactionList: pipeline.reactions,
itemBuilder: (reaction) => ActionCard(
onReorder: () => GetIt.I<AerisAPI>().editPipeline(pipeline),
reactionList: pipeline.reactions,
itemBuilder: (reaction) => ActionCard(
key: ValueKey(pipeline.reactions.indexOf(reaction)),
leading: reaction.service.getLogo(logoSize: 50),
title: reaction.displayName(),
title: reaction.displayName,
trailing: ActionCardPopupMenu(
parentTrigger: pipeline.trigger,
parentReactions: pipeline.reactions,
@@ -174,8 +173,7 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
setState(() {});
GetIt.I<AerisAPI>().editPipeline(pipeline);
}),
)
),
)),
addReactionbutton,
Padding(
padding: const EdgeInsets.only(top: 30, bottom: 5),
+69 -49
View File
@@ -15,10 +15,16 @@ import 'package:skeleton_loader/skeleton_loader.dart';
///Page to setup an action
class SetupActionPage extends StatefulWidget {
const SetupActionPage({Key? key, required this.action, required this.parentReactions, this.parentTrigger}) : super(key: key);
const SetupActionPage(
{Key? key,
required this.action,
required this.parentReactions,
this.parentTrigger})
: super(key: key);
/// Action to setup
final aeris.Action action;
/// Trigger of Parent of the action to setup
final Trigger? parentTrigger;
@@ -37,12 +43,12 @@ class _SetupActionPageState extends State<SetupActionPage> {
void initState() {
super.initState();
serviceState = widget.action.service;
availableActions = GetIt.I<AerisAPI>().getActionsFor(serviceState!, widget.action);
availableActions =
GetIt.I<AerisAPI>().getActionsFor(serviceState!, widget.action);
}
@override
Widget build(BuildContext context) {
final Widget serviceDropdown = DropdownButton<Service>(
value: serviceState,
elevation: 8,
@@ -50,7 +56,8 @@ class _SetupActionPageState extends State<SetupActionPage> {
onChanged: (service) {
setState(() {
serviceState = service;
availableActions = GetIt.I<AerisAPI>().getActionsFor(service!, widget.action);
availableActions =
GetIt.I<AerisAPI>().getActionsFor(service!, widget.action);
});
},
items: Service.all().map<DropdownMenuItem<Service>>((Service service) {
@@ -77,9 +84,10 @@ class _SetupActionPageState extends State<SetupActionPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.action is Trigger
? AppLocalizations.of(context).setupTrigger
: AppLocalizations.of(context).setupReaction,
Text(
widget.action is Trigger
? AppLocalizations.of(context).setupTrigger
: AppLocalizations.of(context).setupReaction,
style: const TextStyle(
fontSize: 25,
)),
@@ -108,49 +116,61 @@ class _SetupActionPageState extends State<SetupActionPage> {
const SizedBox(height: 20),
if (availableActions == null)
SkeletonLoader(
builder: Card(shape: cardShape, child: const SizedBox(height: 40), elevation: 5),
builder: Card(
shape: cardShape,
child: const SizedBox(height: 40),
elevation: 5),
items: 15,
highlightColor: Theme.of(context).colorScheme.secondary
)
else
...[for (ActionTemplate availableAction in availableActions!)
Card(
elevation: 5,
shape: cardShape,
child: ExpandableNotifier(
child: ScrollOnExpand(child: ExpandablePanel(
header: Padding(
padding:
const EdgeInsets.only(left: 30, top: 20, bottom: 20),
child: Text(availableAction.displayName(),
style: const TextStyle(fontSize: 15))),
collapsed: Container(),
expanded: Padding(
padding: const EdgeInsets.all(20),
child: ActionForm(
reactionsCandidates: widget.parentReactions,
triggerCandidate: widget.parentTrigger,
candidate: widget.action,
key: Key("${availableAction.name}${availableAction.description}${availableAction.service}"),
description: availableAction.description!,
name: availableAction.name,
parameters: availableAction.parameters.map((param) {
if (widget.action.service.name == serviceState!.name && widget.action.name == availableAction.name) {
var previousParams = widget.action.parameters.where((element) => element.name == param.name);
if (previousParams.isNotEmpty) {
param.value = previousParams.first.value;
}
}
return param;
}).toList(),
onValidate: (parameters) {
widget.action.service = serviceState!;
widget.action.parameters = ActionParameter.fromJSON(parameters);
widget.action.name = availableAction.name;
Navigator.of(context).pop();
}),
)),
))),
highlightColor: Theme.of(context).colorScheme.secondary)
else ...[
for (ActionTemplate availableAction in availableActions!)
Card(
elevation: 5,
shape: cardShape,
child: ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
header: Padding(
padding: const EdgeInsets.only(
left: 30, top: 20, bottom: 20),
child: Text(availableAction.displayName,
style: const TextStyle(fontSize: 15))),
collapsed: Container(),
expanded: Padding(
padding: const EdgeInsets.all(20),
child: ActionForm(
reactionsCandidates: widget.parentReactions,
triggerCandidate: widget.parentTrigger,
candidate: widget.action,
key: Key(
"${availableAction.name}${availableAction.description}${availableAction.service}"),
description: availableAction.description!,
name: availableAction.name,
parameters:
availableAction.parameters.map((param) {
if (widget.action.service.name ==
serviceState!.name &&
widget.action.name ==
availableAction.name) {
var previousParams = widget.action.parameters
.where((element) =>
element.name == param.name);
if (previousParams.isNotEmpty) {
param.value = previousParams.first.value;
}
}
return param;
}).toList(),
onValidate: (parameters) {
widget.action.service = serviceState!;
widget.action.parameters =
ActionParameter.fromJSON(parameters);
widget.action.name = availableAction.name;
widget.action.displayName = availableAction.displayName;
Navigator.of(context).pop();
}),
)),
))),
const SizedBox(height: 10)
]
],
+89 -79
View File
@@ -13,10 +13,11 @@ import 'package:tuple/tuple.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
class Suggestion extends Tuple3<int, ActionParameter, ActionTemplate> {
Suggestion(int item1, ActionParameter item2, ActionTemplate item3) : super(item1, item2, item3);
Suggestion(int item1, ActionParameter item2, ActionTemplate item3)
: super(item1, item2, item3);
// Overriding show method
@override
String toString() {
return "${item2.name}@$item1";
@@ -27,32 +28,35 @@ class Suggestion extends Tuple3<int, ActionParameter, ActionTemplate> {
class ActionForm extends StatefulWidget {
/// Name of the action
final String name;
/// List of parameters, 'values' are used as default values
final List<ActionParameter> parameters;
/// What the action does
final String description;
/// The Action that will be eventually filled by the form
final aeris.Action candidate;
/// The trigger candidate in the parent form page
final Trigger? triggerCandidate;
/// The trigger candidate in the parent form page
final List<Reaction> reactionsCandidates;
/// On validate callback
final void Function(Map<String, String>) onValidate;
const ActionForm(
{Key? key,
required this.name,
required this.description,
required this.parameters,
required this.onValidate,
required this.candidate,
this.triggerCandidate,
required this.reactionsCandidates,
})
: super(key: key);
const ActionForm({
Key? key,
required this.name,
required this.description,
required this.parameters,
required this.onValidate,
required this.candidate,
this.triggerCandidate,
required this.reactionsCandidates,
}) : super(key: key);
@override
State<ActionForm> createState() => _ActionFormState();
@@ -61,26 +65,27 @@ class ActionForm extends StatefulWidget {
class _ActionFormState extends State<ActionForm> {
final _formKey = GlobalKey<FormState>();
List<Suggestion> getSuggestions(String pattern, ActionCatalogueProvider catalogue) {
List<Suggestion> getSuggestions(
String pattern, ActionCatalogueProvider catalogue) {
List<Suggestion> suggestions = [];
if (pattern.endsWith("{") == false) return suggestions;
if (widget.candidate is Trigger) return suggestions;
if (widget.triggerCandidate != null) {
Trigger trigger = widget.triggerCandidate!;
var triggerTemplate = catalogue.triggerTemplates[trigger.service]!.firstWhere(
(element) => element.name == trigger.name
);
var triggerTemplate = catalogue.triggerTemplates[trigger.service]!
.firstWhere((element) => element.name == trigger.name);
for (var parameter in triggerTemplate.returnedValues) {
suggestions.add(Suggestion(0, parameter, triggerTemplate));
}
}
int index = 1;
int indexOfCandidate = widget.reactionsCandidates.indexOf(widget.candidate as Reaction);
int indexOfCandidate =
widget.reactionsCandidates.indexOf(widget.candidate as Reaction);
for (var reactionCandidate in widget.reactionsCandidates) {
if (index == indexOfCandidate + 1) break;
var reactionTemplate = catalogue.reactionTemplates[reactionCandidate.service]!.firstWhere(
(element) => element.name == reactionCandidate.name
);
var reactionTemplate = catalogue
.reactionTemplates[reactionCandidate.service]!
.firstWhere((element) => element.name == reactionCandidate.name);
for (var parameter in reactionTemplate.returnedValues) {
suggestions.add(Suggestion(index, parameter, reactionTemplate));
}
@@ -94,63 +99,68 @@ class _ActionFormState extends State<ActionForm> {
@override
Widget build(BuildContext context) {
return Consumer<ActionCatalogueProvider>(
builder: (__, catalogue, _) => Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(widget.description, textAlign: TextAlign.left, style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
...widget.parameters.map((param) {
final textEditingController = TextEditingController(text: values[param.name] ?? param.value?.toString());
return TypeAheadFormField<Suggestion>(
key: Key(param.description),
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
controller: textEditingController,
enableSuggestions: widget.candidate is Reaction,
decoration: InputDecoration(
labelText: ReCase(param.name).titleCase,
helperText: param.description
),
),
onSaved: (value) {
values[param.name] = value!;
},
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
elevation: 6,
borderRadius: BorderRadius.all(Radius.circular(10))
),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
hideOnEmpty: true,
suggestionsCallback: (suggestion) => getSuggestions(suggestion, catalogue),
onSuggestionSelected: (suggestion) {
textEditingController.text += suggestion.toString();
textEditingController.text += "}";
values[param.name] = textEditingController.text;
},
itemBuilder: (context, suggestion) => ListTile(
isThreeLine: true,
dense: true,
leading: suggestion.item3.service.getLogo(logoSize: 30),
title: Text(suggestion.item2.name),
subtitle: Text("${suggestion.item2.description}, from '${suggestion.item3.displayName()}'")
));}),
...[
ElevatedButton(
child: Text(AppLocalizations.of(context).save),
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
widget.onValidate(values);
}
},
),
const SizedBox(height: 10)
]
]
)
));
builder: (__, catalogue, _) => Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(widget.description,
textAlign: TextAlign.left,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface)),
...widget.parameters.map((param) {
final textEditingController = TextEditingController(
text: values[param.name] ?? param.value?.toString());
return TypeAheadFormField<Suggestion>(
key: Key(param.description),
textFieldConfiguration: TextFieldConfiguration(
autofocus: true,
controller: textEditingController,
enableSuggestions: widget.candidate is Reaction,
decoration: InputDecoration(
labelText: ReCase(param.name).titleCase,
helperText: param.description),
),
onSaved: (value) {
values[param.name] = value!;
},
suggestionsBoxDecoration:
const SuggestionsBoxDecoration(
elevation: 6,
borderRadius:
BorderRadius.all(Radius.circular(10))),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(context),
]),
hideOnEmpty: true,
suggestionsCallback: (suggestion) =>
getSuggestions(suggestion, catalogue),
onSuggestionSelected: (suggestion) {
textEditingController.text += suggestion.toString();
textEditingController.text += "}";
values[param.name] = textEditingController.text;
},
itemBuilder: (context, suggestion) => ListTile(
isThreeLine: true,
dense: true,
leading:
suggestion.item3.service.getLogo(logoSize: 30),
title: Text(suggestion.item2.name),
subtitle: Text(
"${suggestion.item2.description}, from '${suggestion.item3.displayName}'")));
}),
...[
ElevatedButton(
child: Text(AppLocalizations.of(context).save),
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
widget.onValidate(values);
}
},
),
const SizedBox(height: 10)
]
])));
}
}