refactor: alias/trigger refactors

- combined automation page
- built in triggers
- help
- motd
- triggers/aliases when disconnected
This commit is contained in:
2023-10-31 02:28:23 +02:00
parent 56106f2efe
commit 4496592f67
11 changed files with 466 additions and 467 deletions

View File

@@ -10,6 +10,9 @@ enum MUDActionTarget {
execute,
script,
input,
output,
immediate,
none,
}
class MUDAction {
@@ -67,6 +70,17 @@ class MUDAction {
store.input.text = content;
store.setInput(content);
break;
case MUDActionTarget.output:
debugPrint('ActionSendTo.output: $content');
store.echoOwn(content);
break;
case MUDActionTarget.immediate:
debugPrint('ActionSendTo.immediate: $content');
store.send(content);
break;
case MUDActionTarget.none:
debugPrint('ActionSendTo.none: $content');
break;
}
debugPrint('MUDAction.invoke: done');
}
@@ -89,6 +103,9 @@ class MUDAction {
static String doVariableReplacements(GameStore store, String content) {
debugPrint('MUDAction._doSpecialReplacements: $content');
if (!store.connected) {
return content;
}
content = content
.replaceAll('%PASSWORD', store.currentProfile.password)
.replaceAll('%USERNAME', store.currentProfile.username);
@@ -106,6 +123,13 @@ class NativeMUDAction extends MUDAction {
NativeMUDAction(this.customInvoke)
: super('-- native code --', target: MUDActionTarget.script);
factory NativeMUDAction.echoSystem(String content) => NativeMUDAction((store, matches) {
store.echoSystem(content);
});
factory NativeMUDAction.echo(String content) => NativeMUDAction((store, matches) {
store.echo(content);
});
final void Function(GameStore store, List<String> matches)
customInvoke;

View File

@@ -109,7 +109,7 @@ class AliasProcessResult {
bool lineRemoved;
bool processed;
AliasProcessResult({ this.lineRemoved = false, this.processed = false});
AliasProcessResult({this.lineRemoved = false, this.processed = false});
}
String _key(String str) => 'builtin-alias-$str';
@@ -129,11 +129,12 @@ final builtInAliases = <Alias>[
Alias(
id: _key('help'),
pattern: 'mudhelp',
action: NativeMUDAction(
(store, matches) {
store.echo(BuiltinCommand.help());
},
),
action: NativeMUDAction.echoSystem(BuiltinCommand.help()),
),
Alias(
id: _key('motd'),
pattern: 'mudmotd',
action: NativeMUDAction.echoSystem(BuiltinCommand.motd()),
),
];

View File

@@ -1,20 +1,61 @@
// ignore_for_file: prefer_interpolation_to_compose_strings
import 'package:mudblock/core/string_utils.dart';
class BuiltinCommand {
static const sep =
'--------------------------------------------------------------------------------';
static help() {
const sep =
'--------------------------------------------------------------------------------';
final year = DateTime.now().year;
final years = [2023];
if (!years.contains(year)) {
years.add(year);
}
return '''
$sep
Mudblock - Help
$sep
{_mudblockhelp}
$sep
Mudblock - Help
$sep
Mudblock is a cross-platform MUD client.
See more information at https://github.com/chenasraf/mudblock
Mudblock is a cross-platform MUD client.
See more information at https://github.com/chenasraf/mudblock
$sep
Developed by Chen Asraf
Copyright © $year - All Rights Reserved
$sep
''';
$sep
Developed by Chen Asraf
Copyright © ${years.join('-')} - All Rights Reserved
$sep
To get started, tap the hamburger menu at the top right corner and
select a profile.
You can create, delete or update the profiles as you wish.
Each profile hosts its own settings, aliases, triggers, variables, buttons, and
other similar features.
If you've never played an MUD, try one of the sample MUDs to get started!
$sep
{/_mudblockhelp}
'''
.trimMultiline() +
'\n';
}
static motd() {
return '''
{_mudblockmotd}
$sep
Welcome to MudBlock!
$sep
To get started, tap the hamburger menu at the top right corner and
select a profile.
For more help, type "mudhelp"
$sep
{/_mudblockmotd}
'''
.trimMultiline() +
'\n';
}
}

View File

@@ -62,6 +62,7 @@ class Trigger extends Automation {
if (trigger.matches(str)) {
trigger.invokeEffect(store, str);
if (trigger.isRemovedFromBuffer) {
debugPrint('TriggerProcessResult: lineRemoved = true');
showLine = false;
}
if (trigger.autoDisable) {
@@ -107,3 +108,32 @@ class TriggerProcessResult {
TriggerProcessResult({this.lineRemoved = false});
}
String _key(String str) => 'builtin-trigger-$str';
final builtInTriggers = <Trigger>[
Trigger(
id: _key('mudhelpflag-start'),
pattern: '{_mudblockhelp}',
action: MUDAction.empty(),
isRemovedFromBuffer: true,
),
Trigger(
id: _key('mudhelpflag-end'),
pattern: '{/_mudblockhelp}',
action: MUDAction.empty(),
isRemovedFromBuffer: true,
),
Trigger(
id: _key('mudmotdflag-start'),
pattern: '{_mudblockmotd}',
action: MUDAction.empty(),
isRemovedFromBuffer: true,
),
Trigger(
id: _key('mudmotdflag-end'),
pattern: '{/_mudblockmotd}',
action: MUDAction.empty(),
isRemovedFromBuffer: true,
),
];

View File

@@ -71,7 +71,14 @@ final routes = <String, Widget Function(BuildContext)>{
),
Paths.alias: (context) {
final alias = ModalRoute.of(context)!.settings.arguments as Alias?;
return AliasPage(alias: alias);
return GameStore.consumer(
builder: (context, store, child) => AliasPage(
alias: alias,
onSave: (alias) async {
store.currentProfile.saveAlias(alias);
},
),
);
},
// triggers
@@ -88,7 +95,14 @@ final routes = <String, Widget Function(BuildContext)>{
),
Paths.trigger: (context) {
final trigger = ModalRoute.of(context)!.settings.arguments as Trigger?;
return TriggerPage(trigger: trigger);
return GameStore.consumer(
builder: (context, store, child) => TriggerPage(
trigger: trigger,
onSave: (trigger) async {
store.currentProfile.saveTrigger(trigger);
},
),
);
},
// variables

View File

@@ -16,6 +16,7 @@ import '../core/string_utils.dart';
import 'consts.dart';
import 'features/action.dart';
import 'features/alias.dart';
import 'features/builtin_command.dart';
import 'features/profile.dart';
import 'features/trigger.dart';
import 'routes.dart';
@@ -45,11 +46,14 @@ class GameStore extends ChangeNotifier {
/// accepts csp but NOT double csp
RegExp get outgoingMsgSplitPattern =>
RegExp("(?<!$commandSeparator)$commandSeparator(?!$commandSeparator)");
_outgoingMsgSplitPattern(commandSeparator);
RegExp _outgoingMsgSplitPattern(String csp) =>
RegExp("(?<!$csp)$csp(?!$csp)");
MUDProfile get currentProfile => _currentProfile!;
List<Alias> get aliases => builtInAliases + (_currentProfile?.aliases ?? []);
List<Trigger> get triggers => currentProfile.triggers;
List<Trigger> get triggers =>
builtInTriggers + (_currentProfile?.triggers ?? []);
get connected => _clientReady && _client.connected;
@@ -62,13 +66,7 @@ class GameStore extends ChangeNotifier {
}
void appStart(BuildContext context) async {
echoSystem('''
Welcome to MudBlock!
To get started, tap the hamburger menu at the top right corner and
select a profile.
'''
.trimMultiline());
// For more help, type "mudhelp"
echoSystem(BuiltinCommand.motd());
}
void selectProfileAndConnect(BuildContext context) async {
@@ -162,9 +160,12 @@ class GameStore extends ChangeNotifier {
}
}
void processLines(String text) {
void processLines(String text, {int? color}) {
final lines = text.split(incomingMsgSplitPattern);
for (final (i, line) in lines.indexed) {
for (var (i, line) in lines.indexed) {
if (color != null && !line.startsWith('$esc[')) {
line = '$esc[${color}m$line';
}
onLine(line, newLine: i != lines.length - 1);
}
}
@@ -269,12 +270,12 @@ class GameStore extends ChangeNotifier {
/// echoOwn - same as echo, but with predefined color
void echoOwn(String line) {
echo('$esc[93m$line');
processLines(line, color: 93);
}
/// echoSystem - same as echo, but with predefined color
void echoSystem(String line) {
echo('$esc[96m$line');
processLines(line, color: 96);
}
/// echoError - same as echo, but with predefined color
@@ -284,18 +285,27 @@ class GameStore extends ChangeNotifier {
/// sendBytes - raw send bytes - DOES NOT split by outgoingMsgSplitPattern, no processing
void sendBytes(List<int> bytes) {
if (!_clientReady || !_client.connected) {
return;
}
debugPrint('Sending bytes: $bytes');
_client.sendBytes(bytes);
}
/// sendString - send string - DOES NOT split by outgoingMsgSplitPattern, no processing
void sendString(String line) {
if (!_clientReady || !_client.connected) {
return;
}
debugPrint('Sending string: $line');
_client.send(line + newline);
}
/// send - raw send string - no processing, DOES split by outgoingMsgSplitPattern
void send(String text) {
if (!_clientReady || !_client.connected) {
return;
}
for (var line in _splitCsp(text)) {
if (isCompressed) {
debugPrint('Sending compressed bytes: $line');
@@ -313,7 +323,8 @@ class GameStore extends ChangeNotifier {
line = MUDAction.doVariableReplacements(this, line);
debugPrint('processing aliases for: $line');
var result = Alias.processLine(this, aliases, line);
if (!result.lineRemoved && currentProfile.settings.echoCommands) {
if (!result.lineRemoved &&
(_currentProfile == null || currentProfile.settings.echoCommands)) {
echoOwn(line);
}
if (!result.processed) {
@@ -323,22 +334,23 @@ class GameStore extends ChangeNotifier {
}
List<String> _splitCsp(String line) {
final pattern = _currentProfile != null
? outgoingMsgSplitPattern
: _outgoingMsgSplitPattern(';');
final csp = _currentProfile != null
? currentProfile.settings.commandSeparator
: ';';
return line
.split(outgoingMsgSplitPattern)
.map(
(l) => l.replaceAll(
'$commandSeparator$commandSeparator',
commandSeparator,
),
)
.split(pattern)
.map((l) => l.replaceAll('$csp$csp', csp))
.toList();
}
/// submitInput - echo input, process aliases and triggers, then send, scroll to end, select input
void submitAsInput(String text) {
if (!_clientReady || !_client.connected) {
return;
}
// if (!_clientReady || !_client.connected) {
// return;
// }
execute(text);
scrollToEnd();
selectInput();

View File

@@ -1,202 +1,25 @@
import 'package:flutter/material.dart';
import '../core/features/action.dart';
import '../core/features/alias.dart';
import '../core/platform_utils.dart';
import 'automation_page.dart';
class AliasPage extends StatefulWidget {
const AliasPage({super.key, required this.alias});
class AliasPage extends StatelessWidget {
const AliasPage({
super.key,
this.alias,
required this.onSave,
});
final Alias? alias;
@override
State<AliasPage> createState() => _AliasPageState();
}
class _AliasPageState extends State<AliasPage> {
late final Alias alias;
@override
void initState() {
alias = widget.alias?.copyWith() ?? Alias.empty();
super.initState();
}
final void Function(Alias) onSave;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Alias'),
actions: [
Switch.adaptive(
value: alias.enabled,
onChanged: (value) async {
alias.enabled = value;
},
)
],
),
body: Align(
alignment: Alignment.topCenter,
child: SizedBox(
width: 1200,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Builder(
builder: (context) {
final patternTextField = TextField(
controller: TextEditingController(text: alias.pattern),
decoration: const InputDecoration(
labelText: 'Pattern',
helperText: 'The pattern to match your input against',
),
onChanged: (value) {
alias.pattern = value;
},
);
final regexCheckbox = CheckboxListTile(
title: const Text('Regular Expression'),
// subtitle: const Text(
// 'Whether the pattern is a regular expression',
// ),
value: alias.isRegex,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
alias.isRegex = value ?? false;
});
},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (PlatformUtils.isDesktop)
Row(
children: [
Expanded(
child: patternTextField,
),
SizedBox(
width: 270,
child: regexCheckbox,
),
],
),
if (!PlatformUtils.isDesktop) ...[
patternTextField,
regexCheckbox,
],
TextField(
controller: TextEditingController(text: alias.label),
decoration: const InputDecoration(
labelText: 'Label',
helperText: 'Can be used to identify the alias in scripts',
),
onChanged: (value) {
alias.label = value;
},
),
TextField(
controller:
TextEditingController(text: alias.action.content),
minLines: PlatformUtils.isDesktop ? 20 : 5,
maxLines: PlatformUtils.isDesktop ? 50 : 20,
decoration: const InputDecoration(
labelText: 'Action',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
onChanged: (value) {
alias.action.content = value;
},
),
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: Text(
'Send To',
style: Theme.of(context).textTheme.bodySmall,
),
),
DropdownMenu(
initialSelection: alias.action.target,
onSelected: (value) {
if (value is MUDActionTarget) {
alias.action.target = value;
}
},
dropdownMenuEntries: const [
DropdownMenuEntry(
value: MUDActionTarget.world,
label: 'World',
),
DropdownMenuEntry(
value: MUDActionTarget.execute,
label: 'Execute',
),
DropdownMenuEntry(
value: MUDActionTarget.script,
label: 'Script',
),
DropdownMenuEntry(
value: MUDActionTarget.input,
label: 'Command Input',
),
],
),
CheckboxListTile(
title: const Text('Case Sensitive'),
subtitle: const Text(
'If checked, the pattern will only match if the case matches.',
),
value: alias.isCaseSensitive,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
alias.isCaseSensitive = value ?? false;
});
},
),
CheckboxListTile(
title: const Text('Remove From Output'),
subtitle: const Text(
'If checked, your input line will be removed from the buffer when it is matched.',
),
value: alias.isRemovedFromBuffer,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
alias.isRemovedFromBuffer = value ?? false;
});
},
),
CheckboxListTile(
title: const Text('Auto Disable'),
subtitle: const Text(
'If checked, the alias will be disabled after it is matched once.',
),
value: alias.autoDisable,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
alias.autoDisable = value ?? false;
});
},
),
],
);
},
),
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context, alias);
},
child: const Icon(Icons.save),
),
return AutomationPage<Alias>(
automation: alias,
title: 'Alias',
onSave: onSave,
create: (alias) => alias?.copyWith() ?? Alias.empty(),
);
}
}

View File

@@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import '../core/features/action.dart';
import '../core/features/alias.dart';
import '../core/features/automation.dart';
import '../core/platform_utils.dart';
class AutomationPage<T extends Automation> extends StatefulWidget {
const AutomationPage({
super.key,
required this.automation,
required this.create,
required this.title,
required this.onSave,
});
final T? automation;
final T Function(T? automation) create;
final void Function(T automation) onSave;
final String title;
@override
State<AutomationPage> createState() => _AutomationPageState<T>();
}
class _AutomationPageState<T extends Automation>
extends State<AutomationPage<T>> {
late final T automation;
@override
void initState() {
automation = widget.create(widget.automation);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
Switch.adaptive(
value: automation.enabled,
onChanged: (value) async {
automation.enabled = value;
},
)
],
),
body: Align(
alignment: Alignment.topCenter,
child: SizedBox(
width: 1200,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Builder(
builder: (context) {
var automationMatcheeStr =
T == Alias ? 'your input' : 'the output';
var automationTypeStr = T == Alias ? 'alias' : 'trigger';
final patternTextField = TextField(
controller: TextEditingController(text: automation.pattern),
decoration: InputDecoration(
labelText: 'Pattern',
helperText:
'The pattern to match $automationMatcheeStr against',
),
onChanged: (value) {
automation.pattern = value;
},
);
final regexCheckbox = CheckboxListTile(
title: const Text('Regular Expression'),
// subtitle: const Text(
// 'Whether the pattern is a regular expression',
// ),
value: automation.isRegex,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
automation.isRegex = value ?? false;
});
},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (PlatformUtils.isDesktop)
Row(
children: [
Expanded(
child: patternTextField,
),
SizedBox(
width: 270,
child: regexCheckbox,
),
],
),
if (!PlatformUtils.isDesktop) ...[
patternTextField,
regexCheckbox,
],
TextField(
controller:
TextEditingController(text: automation.label),
decoration: InputDecoration(
labelText: 'Label',
helperText:
'Can be used to identify the $automationTypeStr in scripts',
),
onChanged: (value) {
automation.label = value;
},
),
TextField(
controller: TextEditingController(
text: automation.action.content,
),
minLines: PlatformUtils.isDesktop ? 20 : 5,
maxLines: PlatformUtils.isDesktop ? 50 : 20,
decoration: const InputDecoration(
labelText: 'Action',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
onChanged: (value) {
automation.action.content = value;
},
),
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: Text(
'Send To',
style: Theme.of(context).textTheme.bodySmall,
),
),
DropdownMenu(
initialSelection: automation.action.target,
onSelected: (value) {
if (value is MUDActionTarget) {
automation.action.target = value;
}
},
dropdownMenuEntries: const [
DropdownMenuEntry(
value: MUDActionTarget.world,
label: 'World',
),
DropdownMenuEntry(
value: MUDActionTarget.execute,
label: 'Execute',
),
DropdownMenuEntry(
value: MUDActionTarget.script,
label: 'Script',
),
DropdownMenuEntry(
value: MUDActionTarget.input,
label: 'Command Input',
),
DropdownMenuEntry(
value: MUDActionTarget.immediate,
label: 'Immediate',
),
DropdownMenuEntry(
value: MUDActionTarget.output,
label: 'Output',
),
],
),
CheckboxListTile(
title: const Text('Case Sensitive'),
subtitle: const Text(
'If checked, the pattern will only match if the case matches.',
),
value: automation.isCaseSensitive,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
automation.isCaseSensitive = value ?? false;
});
},
),
CheckboxListTile(
title: const Text('Remove From Output'),
subtitle: Text(
'If checked, $automationMatcheeStr line will be removed from the buffer when it is matched.',
),
value: automation.isRemovedFromBuffer,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
automation.isRemovedFromBuffer = value ?? false;
});
},
),
CheckboxListTile(
title: const Text('Auto Disable'),
subtitle: Text(
'If checked, the $automationTypeStr will be disabled after it is matched once.',
),
value: automation.autoDisable,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
automation.autoDisable = value ?? false;
});
},
),
],
);
},
),
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context, automation);
},
child: const Icon(Icons.save),
),
);
}
}

View File

@@ -34,16 +34,16 @@ class HomePageState extends State<HomePage>
super.dispose();
}
final consoleStyle = const TextStyle(
color: Colors.white,
fontFamily: 'Fira Code',
fontSize: 16,
height: 1,
);
TextStyle get inputStyle => consoleStyle.copyWith(color: Colors.grey);
@override
Widget build(BuildContext context) {
const consoleStyle = TextStyle(
color: Colors.white,
fontFamily: 'Fira Code',
fontSize: 16,
height: 1,
);
final inputStyle = consoleStyle.copyWith(color: Colors.grey);
return Shortcuts(
shortcuts: numpadKeysIntentMap,
child: Actions(
@@ -78,42 +78,7 @@ class HomePageState extends State<HomePage>
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Focus(
child: SelectableText.rich(
TextSpan(
children: [
for (final line in lines) ...[
for (final segment
in ColorUtils.split(line))
TextSpan(
text: segment.text,
style: consoleStyle.copyWith(
color: Color(
segment.themedFgColor),
backgroundColor: Color(
segment.themedBgColor),
fontWeight: segment.bold
? FontWeight.w900
: FontWeight.w400,
fontStyle: segment.italic
? FontStyle.italic
: null,
decoration: segment.underline
? TextDecoration.underline
: null,
),
),
// const TextSpan(
// text: newline,
// style:
// consoleStyle, // .copyWith(fontSize: 1),
// ),
],
],
),
enableInteractiveSelection: true,
selectionWidthStyle: BoxWidthStyle.tight,
selectionHeightStyle: BoxHeightStyle.max,
),
child: _buildGameOutput(lines),
),
),
),
@@ -156,6 +121,37 @@ class HomePageState extends State<HomePage>
);
}
SelectableText _buildGameOutput(List<String> lines) {
return SelectableText.rich(
TextSpan(
children: [
for (final line in lines) ...[
for (final segment in ColorUtils.split(line))
TextSpan(
text: segment.text,
style: consoleStyle.copyWith(
color: Color(segment.themedFgColor),
backgroundColor: Color(segment.themedBgColor),
fontWeight: segment.bold ? FontWeight.w900 : FontWeight.w400,
fontStyle: segment.italic ? FontStyle.italic : null,
decoration:
segment.underline ? TextDecoration.underline : null,
),
),
// const TextSpan(
// text: newline,
// style:
// consoleStyle, // .copyWith(fontSize: 1),
// ),
],
],
),
enableInteractiveSelection: true,
selectionWidthStyle: BoxWidthStyle.tight,
selectionHeightStyle: BoxHeightStyle.max,
);
}
@override
onWindowBlur() {
debugPrint("Window blurred");

View File

@@ -106,6 +106,11 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
onTap: () => Navigator.pushNamed(context, Paths.shortcuts),
),
const Divider(),
ListTile(
title: const Text('Profiles'),
leading: const Icon(Icons.cable),
onTap: () => Navigator.pushNamed(context, Paths.profiles),
),
ListTile(
title: const Text('Settings'),
leading: const Icon(Icons.settings),

View File

@@ -1,201 +1,26 @@
import 'package:flutter/material.dart';
import '../core/features/action.dart';
import '../core/features/trigger.dart';
import '../core/platform_utils.dart';
import 'automation_page.dart';
class TriggerPage extends StatefulWidget {
const TriggerPage({super.key, required this.trigger});
class TriggerPage extends StatelessWidget {
const TriggerPage({
super.key,
this.trigger,
required this.onSave,
});
final Trigger? trigger;
@override
State<TriggerPage> createState() => _TriggerPageState();
}
class _TriggerPageState extends State<TriggerPage> {
late final Trigger trigger;
@override
void initState() {
trigger = widget.trigger?.copyWith() ?? Trigger.empty();
super.initState();
}
final void Function(Trigger) onSave;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Trigger'),
actions: [
Switch.adaptive(
value: trigger.enabled,
onChanged: (value) {
trigger.enabled = value;
},
)
],
),
body: Align(
alignment: Alignment.topCenter,
child: SizedBox(
width: 1200,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Builder(
builder: (context) {
final patternTextField = TextField(
controller: TextEditingController(text: trigger.pattern),
decoration: const InputDecoration(
labelText: 'Pattern',
helperText: 'The pattern to match your input against',
),
onChanged: (value) {
trigger.pattern = value;
},
);
final regexCheckbox = CheckboxListTile(
title: const Text('Regular Expression'),
// subtitle: const Text(
// 'Whether the pattern is a regular expression',
// ),
value: trigger.isRegex,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
trigger.isRegex = value ?? false;
});
},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (PlatformUtils.isDesktop)
Row(
children: [
Expanded(
child: patternTextField,
),
SizedBox(
width: 270,
child: regexCheckbox,
),
],
),
if (!PlatformUtils.isDesktop) ...[
patternTextField,
regexCheckbox,
],
TextField(
controller: TextEditingController(text: trigger.label),
decoration: const InputDecoration(
labelText: 'Label',
helperText: 'Can be used to identify the trigger in scripts',
),
onChanged: (value) {
trigger.label = value;
},
),
TextField(
controller:
TextEditingController(text: trigger.action.content),
minLines: PlatformUtils.isDesktop ? 20 : 5,
maxLines: PlatformUtils.isDesktop ? 50 : 20,
decoration: const InputDecoration(
labelText: 'Action',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
onChanged: (value) {
trigger.action.content = value;
},
),
Padding(
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
child: Text(
'Send To',
style: Theme.of(context).textTheme.bodySmall,
),
),
DropdownMenu(
initialSelection: trigger.action.target,
onSelected: (value) {
if (value is MUDActionTarget) {
trigger.action.target = value;
}
},
dropdownMenuEntries: const [
DropdownMenuEntry(
value: MUDActionTarget.world,
label: 'World',
),
DropdownMenuEntry(
value: MUDActionTarget.execute,
label: 'Execute',
),
DropdownMenuEntry(
value: MUDActionTarget.script,
label: 'Script',
),
DropdownMenuEntry(
value: MUDActionTarget.input,
label: 'Command Input',
),
],
),
CheckboxListTile(
title: const Text('Case Sensitive'),
subtitle: const Text(
'If checked, the pattern will only match if the case matches.',
),
value: trigger.isCaseSensitive,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
trigger.isCaseSensitive = value ?? false;
});
},
),
CheckboxListTile(
title: const Text('Remove From Output'),
subtitle: const Text(
'If checked, the output line will be removed from the buffer when it is matched.',
),
value: trigger.isRemovedFromBuffer,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
trigger.isRemovedFromBuffer = value ?? false;
});
},
),
CheckboxListTile(
title: const Text('Auto Disable'),
subtitle: const Text(
'If checked, the trigger will be disabled after it is matched once.',
),
value: trigger.autoDisable,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (value) {
setState(() {
trigger.autoDisable = value ?? false;
});
},
),
],
);
},
),
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pop(context, trigger);
},
child: const Icon(Icons.save),
),
return AutomationPage<Trigger>(
automation: trigger,
title: 'Trigger',
onSave: onSave,
create: (trigger) => trigger?.copyWith() ?? Trigger.empty(),
);
}
}