mirror of
https://github.com/chenasraf/mudblock.git
synced 2026-05-18 01:48:57 +00:00
feat: global variables support
This commit is contained in:
@@ -13,8 +13,8 @@ enum MUDActionTarget {
|
||||
|
||||
class MUDAction {
|
||||
String content;
|
||||
MUDActionTarget sendTo;
|
||||
MUDAction(this.content, {this.sendTo = MUDActionTarget.world});
|
||||
MUDActionTarget target;
|
||||
MUDAction(this.content, {this.target = MUDActionTarget.world});
|
||||
|
||||
void invoke(GameStore store, List<String> matches) {
|
||||
debugPrint('MUDAction.invoke: ${this.content}, $matches');
|
||||
@@ -25,7 +25,7 @@ class MUDAction {
|
||||
content = _doSpecialReplacements(store, content);
|
||||
debugPrint('MUDAction.invoking: $content');
|
||||
|
||||
switch (sendTo) {
|
||||
switch (target) {
|
||||
case MUDActionTarget.world:
|
||||
debugPrint('ActionSendTo.world: $content');
|
||||
store.send(content);
|
||||
@@ -57,22 +57,29 @@ class MUDAction {
|
||||
|
||||
factory MUDAction.fromJson(Map<String, dynamic> json) => MUDAction(
|
||||
json['content'],
|
||||
sendTo: MUDActionTarget.values[json['sendTo']],
|
||||
// TODO generalize getting enum from string
|
||||
target: MUDActionTarget.values.firstWhere(
|
||||
(e) => e.name == json['target'],
|
||||
orElse: () => MUDActionTarget.world,
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'content': content,
|
||||
'sendTo': sendTo.index,
|
||||
'target': target.name,
|
||||
};
|
||||
|
||||
String _doSpecialReplacements(GameStore store, String content) {
|
||||
debugPrint('MUDAction._doSpecialReplacements: $content');
|
||||
debugPrint("password: ${store.currentProfile.password}");
|
||||
return content
|
||||
content = content
|
||||
.replaceAll('%PASSWORD', store.currentProfile.password)
|
||||
.replaceAll('%USERNAME', store.currentProfile.username)
|
||||
//
|
||||
;
|
||||
for (final vari in store.variables) {
|
||||
content = content.replaceAll('%${vari.name}', vari.value);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:encrypt/encrypt.dart' as enc;
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
@@ -9,6 +7,7 @@ import '../storage.dart';
|
||||
import '../string_utils.dart';
|
||||
import 'alias.dart';
|
||||
import 'trigger.dart';
|
||||
import 'variable.dart';
|
||||
|
||||
class MUDProfile {
|
||||
String id;
|
||||
@@ -18,6 +17,7 @@ class MUDProfile {
|
||||
bool mccpEnabled;
|
||||
String username;
|
||||
String password;
|
||||
AuthMethod authMethod;
|
||||
|
||||
MUDProfile({
|
||||
required this.id,
|
||||
@@ -27,6 +27,7 @@ class MUDProfile {
|
||||
this.mccpEnabled = true,
|
||||
this.username = '',
|
||||
this.password = '',
|
||||
this.authMethod = AuthMethod.none,
|
||||
});
|
||||
|
||||
factory MUDProfile.empty() => MUDProfile(
|
||||
@@ -44,6 +45,7 @@ class MUDProfile {
|
||||
bool? mccpEnabled,
|
||||
String? username,
|
||||
String? password,
|
||||
AuthMethod? authMethod,
|
||||
}) =>
|
||||
MUDProfile(
|
||||
id: id ?? this.id,
|
||||
@@ -53,6 +55,7 @@ class MUDProfile {
|
||||
mccpEnabled: mccpEnabled ?? this.mccpEnabled,
|
||||
username: username ?? this.username,
|
||||
password: password ?? this.password,
|
||||
authMethod: authMethod ?? this.authMethod,
|
||||
);
|
||||
|
||||
factory MUDProfile.fromJson(Map<String, dynamic> json) {
|
||||
@@ -64,6 +67,11 @@ class MUDProfile {
|
||||
mccpEnabled: json['mccpEnabled'],
|
||||
username: json['username'],
|
||||
password: decrypt(json['password']),
|
||||
// TODO generalize getting enum from string
|
||||
authMethod: AuthMethod.values.firstWhere(
|
||||
(e) => e.name == json['authMethod'],
|
||||
orElse: () => AuthMethod.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,18 +83,19 @@ class MUDProfile {
|
||||
'mccpEnabled': mccpEnabled,
|
||||
'username': username,
|
||||
'password': encrypt(password),
|
||||
'authMethod': authMethod.name,
|
||||
};
|
||||
|
||||
static Future<void> save(MUDProfile profile) async {
|
||||
debugPrint('MUDProfile.save: ${profile.id}');
|
||||
return ProfileStorage.writeProfileFile(
|
||||
profile.id, profile.id, jsonEncode(profile.toJson()));
|
||||
profile.id, profile.id, (profile.toJson()));
|
||||
}
|
||||
|
||||
Future<List<Trigger>> loadTriggers() async {
|
||||
debugPrint('MUDProfile.loadTriggers: $id');
|
||||
final triggers = await ProfileStorage.listProfileFiles(id, 'triggers');
|
||||
final triggerFiles = <String>[];
|
||||
final triggerFiles = <Map<String, dynamic>>[];
|
||||
for (final trigger in triggers) {
|
||||
debugPrint('MUDProfile.loadTriggers: $id/triggers/$trigger');
|
||||
final triggerFile =
|
||||
@@ -95,13 +104,13 @@ class MUDProfile {
|
||||
triggerFiles.add(triggerFile);
|
||||
}
|
||||
}
|
||||
return triggerFiles.map((e) => Trigger.fromJson(jsonDecode(e))).toList();
|
||||
return triggerFiles.map((e) => Trigger.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<List<Alias>> loadAliases() async {
|
||||
debugPrint('MUDProfile.loadAliases: $id');
|
||||
final aliases = await ProfileStorage.listProfileFiles(id, 'aliases');
|
||||
final aliasFiles = <String>[];
|
||||
final aliasFiles = <Map<String, dynamic>>[];
|
||||
for (final alias in aliases) {
|
||||
debugPrint('MUDProfile.loadAliases: $id/aliases/$alias');
|
||||
final aliasFile =
|
||||
@@ -110,19 +119,47 @@ class MUDProfile {
|
||||
aliasFiles.add(aliasFile);
|
||||
}
|
||||
}
|
||||
return aliasFiles.map((e) => Alias.fromJson(jsonDecode(e))).toList();
|
||||
return aliasFiles.map((e) => Alias.fromJson(e)).toList();
|
||||
}
|
||||
|
||||
Future<List<Variable>> loadVariables() async {
|
||||
debugPrint('MUDProfile.loadVariables: $id');
|
||||
final vars = await ProfileStorage.readProfileFile(id, 'vars');
|
||||
if (vars == null) {
|
||||
return [];
|
||||
}
|
||||
return (vars['vars'] as List<dynamic>)
|
||||
.map((e) => Variable.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> saveAlias(Alias alias) async {
|
||||
debugPrint('MUDProfile.saveAlias: $id/aliases/${alias.id}');
|
||||
return ProfileStorage.writeProfileFile(
|
||||
id, 'aliases/${alias.id}', jsonEncode(alias.toJson()));
|
||||
id, 'aliases/${alias.id}', alias.toJson());
|
||||
}
|
||||
|
||||
Future<void> saveTrigger(Trigger trigger) async {
|
||||
debugPrint('MUDProfile.saveTrigger: $id/triggers/${trigger.id}');
|
||||
return ProfileStorage.writeProfileFile(
|
||||
id, 'triggers/${trigger.id}', jsonEncode(trigger.toJson()));
|
||||
id, 'triggers/${trigger.id}', trigger.toJson());
|
||||
}
|
||||
|
||||
Future<void> saveVariable(List<Variable> current, Variable update) async {
|
||||
debugPrint('MUDProfile.saveVariable: $id/vars');
|
||||
final existing = current.indexWhere(
|
||||
(v) => v.name == update.name,
|
||||
);
|
||||
if (existing >= 0) {
|
||||
current[existing] = update;
|
||||
} else {
|
||||
current.add(update);
|
||||
}
|
||||
return ProfileStorage.writeProfileFile(
|
||||
id,
|
||||
'vars',
|
||||
{'vars': current.map((v) => v.toJson()).toList()},
|
||||
);
|
||||
}
|
||||
|
||||
static final encKey = enc.Key.fromUtf8(pwdKey);
|
||||
@@ -158,3 +195,8 @@ class MUDProfile {
|
||||
}
|
||||
}
|
||||
|
||||
enum AuthMethod {
|
||||
none,
|
||||
diku,
|
||||
}
|
||||
|
||||
|
||||
94
lib/core/features/variable.dart
Normal file
94
lib/core/features/variable.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
class Variable<T> {
|
||||
String name;
|
||||
VariableType type;
|
||||
String strValue;
|
||||
|
||||
Variable(this.name, this.type, this.strValue);
|
||||
Variable.empty()
|
||||
: type = VariableType.string,
|
||||
name = '',
|
||||
strValue = '';
|
||||
Variable.string(this.name, this.strValue) : type = VariableType.string;
|
||||
Variable.number(this.name, this.strValue) : type = VariableType.number;
|
||||
Variable.boolean(this.name, this.strValue) : type = VariableType.boolean;
|
||||
|
||||
T get value {
|
||||
switch (type) {
|
||||
case VariableType.string:
|
||||
return strValue as T;
|
||||
case VariableType.number:
|
||||
return double.parse(strValue) as T;
|
||||
case VariableType.boolean:
|
||||
return (strValue == 'true') as T;
|
||||
}
|
||||
}
|
||||
|
||||
set value(T value) {
|
||||
switch (type) {
|
||||
case VariableType.string:
|
||||
strValue = value as String;
|
||||
break;
|
||||
case VariableType.number:
|
||||
strValue = (value as double).toString();
|
||||
break;
|
||||
case VariableType.boolean:
|
||||
strValue = (value as bool).toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
factory Variable.fromJson(Map<String, dynamic> json) {
|
||||
// TODO generalize getting enum from string
|
||||
switch (VariableType.values.firstWhere(
|
||||
(e) => e.name == json['type'],
|
||||
orElse: () => VariableType.string,
|
||||
)) {
|
||||
case VariableType.string:
|
||||
return Variable.string(json['name'], json['value']);
|
||||
case VariableType.number:
|
||||
return Variable.number(json['name'], json['value']);
|
||||
case VariableType.boolean:
|
||||
return Variable.boolean(json['name'], json['value']);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'type': type.name,
|
||||
'value': strValue,
|
||||
};
|
||||
|
||||
Variable copyWith<T>({
|
||||
String? name,
|
||||
VariableType? type,
|
||||
String? strValue,
|
||||
}) =>
|
||||
Variable(
|
||||
name ?? this.name,
|
||||
type ?? this.type,
|
||||
strValue ?? this.strValue,
|
||||
);
|
||||
}
|
||||
|
||||
class StringVariable extends Variable<String> {
|
||||
StringVariable(String name, String value) : super.string(name, value);
|
||||
StringVariable.fromJson(Map<String, dynamic> json)
|
||||
: super(json['name'], VariableType.values[json['type']], json['value']);
|
||||
}
|
||||
|
||||
class NumberVariable extends Variable<double> {
|
||||
NumberVariable(String name, double value)
|
||||
: super.number(name, value.toString());
|
||||
NumberVariable.fromJson(Map<String, dynamic> json)
|
||||
: super(json['name'], VariableType.values[json['type']], json['value']);
|
||||
}
|
||||
|
||||
class BooleanVariable extends Variable<bool> {
|
||||
BooleanVariable(String name, bool value)
|
||||
: super.boolean(name, value.toString());
|
||||
BooleanVariable.fromJson(Map<String, dynamic> json)
|
||||
: super(json['name'], VariableType.values[json['type']], json['value']);
|
||||
}
|
||||
|
||||
enum VariableType { string, number, boolean }
|
||||
|
||||
@@ -12,12 +12,14 @@ final profilePresets = [
|
||||
name: 'SimpleMUD',
|
||||
host: 'smud.ourmmo.com',
|
||||
port: 3000,
|
||||
authMethod: AuthMethod.diku,
|
||||
),
|
||||
MUDProfile(
|
||||
id: 'aardwolf',
|
||||
name: 'Aardwolf',
|
||||
host: 'aardmud.org',
|
||||
port: 23,
|
||||
authMethod: AuthMethod.diku,
|
||||
),
|
||||
MUDProfile(
|
||||
id: 'batmud',
|
||||
|
||||
@@ -12,16 +12,26 @@ import '../pages/profile_page.dart';
|
||||
import '../pages/select_profile_page.dart';
|
||||
import '../pages/trigger_list_page.dart';
|
||||
import '../pages/trigger_page.dart';
|
||||
import '../pages/variable_list_page.dart';
|
||||
import '../pages/variable_page.dart';
|
||||
import 'features/profile.dart';
|
||||
import 'features/variable.dart';
|
||||
|
||||
class Paths {
|
||||
static const home = '/';
|
||||
|
||||
static const profiles = '/profiles';
|
||||
static const profile = '/profile';
|
||||
|
||||
static const aliases = '/aliases';
|
||||
static const alias = '/alias';
|
||||
|
||||
static const triggers = '/triggers';
|
||||
static const trigger = '/trigger';
|
||||
static const home = '/home';
|
||||
|
||||
static const variables = '/variables';
|
||||
static const variable = '/variable';
|
||||
|
||||
static const settings = '/settings';
|
||||
}
|
||||
|
||||
@@ -49,6 +59,15 @@ final routes = <String, Widget Function(BuildContext)>{
|
||||
final trigger = ModalRoute.of(context)!.settings.arguments as Trigger?;
|
||||
return TriggerPage(trigger: trigger);
|
||||
},
|
||||
Paths.variables: (context) => GameStore.consumer(
|
||||
builder: (context, store, child) {
|
||||
return const VariableListPage();
|
||||
},
|
||||
),
|
||||
Paths.variable: (context) {
|
||||
final variable = ModalRoute.of(context)!.settings.arguments as Variable?;
|
||||
return VariablePage(variable: variable);
|
||||
},
|
||||
Paths.home: (context) => HomeScaffold(
|
||||
builder: (context, _) {
|
||||
return HomePage(key: homeKey);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -15,9 +16,6 @@ class FileStorage {
|
||||
|
||||
static Future<String?> readFile(String filename) async {
|
||||
debugPrint('Getting file: $filename');
|
||||
// final collection = path.dirname(filename);
|
||||
// filename = path.basename(filename);
|
||||
// return _store.collection(collection).doc(filename).get();
|
||||
final file = File(path.join(base, filename));
|
||||
var exists = await file.exists();
|
||||
if (!exists) {
|
||||
@@ -30,9 +28,6 @@ class FileStorage {
|
||||
static Future<void> writeFile(String filename, String data) async {
|
||||
debugPrint(
|
||||
'Setting file: $filename, data: ${data.toString().length} bytes');
|
||||
// final collection = path.dirname(filename);
|
||||
// filename = path.basename(filename);
|
||||
// await _store.collection(collection).doc(filename).set(data);
|
||||
final file = File(path.join(base, filename));
|
||||
await file.create(recursive: true);
|
||||
await file.writeAsString(data);
|
||||
@@ -40,9 +35,6 @@ class FileStorage {
|
||||
|
||||
static Future<void> deleteFile(String filename) async {
|
||||
debugPrint('Deleting file: $filename');
|
||||
// final collection = path.dirname(filename);
|
||||
// filename = path.basename(filename);
|
||||
// await _store.collection(collection).doc(filename).delete();
|
||||
final file = File(path.join(base, filename));
|
||||
await file.delete();
|
||||
}
|
||||
@@ -50,9 +42,6 @@ class FileStorage {
|
||||
static Future<List<String>> readDirectory(
|
||||
String collection,
|
||||
) async {
|
||||
// final docs = await _store.collection(collection).get();
|
||||
// debugPrint('Listing collection: $collection, ${docs?.length} docs');
|
||||
// return (docs ?? {}).cast<String, String>();
|
||||
final dir = Directory(path.join(base, collection));
|
||||
var exists = await dir.exists();
|
||||
if (!exists) {
|
||||
@@ -65,29 +54,30 @@ class FileStorage {
|
||||
|
||||
static Future<void> deleteDirectory(String collection) async {
|
||||
debugPrint('Clearing collection: $collection');
|
||||
// await _store.collection(collection).delete();
|
||||
final dir = Directory(path.join(base, collection));
|
||||
await dir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileStorage {
|
||||
static Future<String?> readProfileFile(
|
||||
static Future<Map<String, dynamic>?> readProfileFile(
|
||||
String profile, String filename) async {
|
||||
return FileStorage.readFile('profiles/$profile/$filename');
|
||||
final data = await FileStorage.readFile('profiles/$profile/$filename.json');
|
||||
return data != null ? jsonDecode(data) : null;
|
||||
}
|
||||
|
||||
static Future<void> writeProfileFile(
|
||||
String profile, String filename, String data) async {
|
||||
await FileStorage.writeFile('profiles/$profile/$filename', data);
|
||||
String profile, String filename, dynamic data) async {
|
||||
data = jsonEncode(data);
|
||||
await FileStorage.writeFile('profiles/$profile/$filename.json', data);
|
||||
}
|
||||
|
||||
static Future<void> deleteProfile(String profile) async {
|
||||
await FileStorage.deleteDirectory('profiles/$profile');
|
||||
await FileStorage.deleteDirectory('profiles/$profile.json');
|
||||
}
|
||||
|
||||
static Future<void> deleteProfileFile(String profile, String filename) async {
|
||||
await FileStorage.deleteFile('profiles/$profile/$filename');
|
||||
await FileStorage.deleteFile('profiles/$profile/$filename.json');
|
||||
}
|
||||
|
||||
static Future<List<String>> listAllProfiles() async {
|
||||
@@ -95,6 +85,7 @@ class ProfileStorage {
|
||||
return list
|
||||
.where((f) =>
|
||||
Directory(path.join(FileStorage.base, 'profiles', f)).existsSync())
|
||||
.map((f) => path.withoutExtension(f))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -102,8 +93,9 @@ class ProfileStorage {
|
||||
String profile, [
|
||||
String? directory,
|
||||
]) async {
|
||||
return FileStorage.readDirectory(
|
||||
final list = await FileStorage.readDirectory(
|
||||
'profiles/$profile${directory != null ? '/$directory' : ''}');
|
||||
return list.map((f) => path.withoutExtension(f)).toList();
|
||||
}
|
||||
|
||||
static Future<void> deleteAllProfiles() async {
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'consts.dart';
|
||||
import 'features/alias.dart';
|
||||
import 'features/profile.dart';
|
||||
import 'features/trigger.dart';
|
||||
import 'features/variable.dart';
|
||||
|
||||
const maxLines = 2000;
|
||||
|
||||
@@ -26,8 +27,6 @@ class GameStore extends ChangeNotifier {
|
||||
final FocusNode inputFocus = FocusNode();
|
||||
bool isCompressed = false;
|
||||
final ZLibDecoder decoder = ZLibDecoder();
|
||||
final List<Trigger> triggers = [];
|
||||
final List<Alias> aliases = [];
|
||||
final msgSplitPattern = RegExp("($cr$lf)|($lf$cr)|$cr|$lf");
|
||||
final ZLibCodec _decoder = ZLibCodec();
|
||||
final StreamController<List<int>> _rawStreamController = StreamController();
|
||||
@@ -37,6 +36,11 @@ class GameStore extends ChangeNotifier {
|
||||
MUDProfile? _currentProfile;
|
||||
bool _clientReady = false;
|
||||
|
||||
// features
|
||||
final List<Trigger> triggers = [];
|
||||
final List<Alias> aliases = [];
|
||||
final List<Variable> variables = [];
|
||||
|
||||
MUDProfile get currentProfile => _currentProfile!;
|
||||
|
||||
Future<GameStore> init() async {
|
||||
@@ -67,6 +71,7 @@ class GameStore extends ChangeNotifier {
|
||||
await Future.wait([
|
||||
loadTriggers(),
|
||||
loadAliases(),
|
||||
loadVariables(),
|
||||
]);
|
||||
_client.connect();
|
||||
}
|
||||
@@ -88,6 +93,14 @@ class GameStore extends ChangeNotifier {
|
||||
debugPrint('Aliases: ${aliases.length}');
|
||||
}
|
||||
|
||||
Future<void> loadVariables() async {
|
||||
final list = await currentProfile.loadVariables();
|
||||
variables.clear();
|
||||
variables.addAll(list);
|
||||
notifyListeners();
|
||||
debugPrint('Variables: ${variables.length}');
|
||||
}
|
||||
|
||||
bool processTriggers(String line) {
|
||||
bool showLine = true;
|
||||
final str = ColorUtils.stripColor(line);
|
||||
@@ -126,9 +139,18 @@ class GameStore extends ChangeNotifier {
|
||||
return sendLine;
|
||||
}
|
||||
|
||||
void _onConnect() {
|
||||
Future<void> _onConnect() async {
|
||||
_clientReady = true;
|
||||
echo('Connected');
|
||||
if (currentProfile.authMethod != AuthMethod.none && currentProfile.username.isNotEmpty && currentProfile.password.isNotEmpty) {
|
||||
// _client.doo(90);
|
||||
// sendBytes([Symbols.iac, Symbols.doo, 90]);
|
||||
debugPrint('Sending username and password');
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
send(currentProfile.username);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
send(currentProfile.password);
|
||||
}
|
||||
}
|
||||
|
||||
void requestMCCP() {
|
||||
@@ -326,7 +348,7 @@ class GameStore extends ChangeNotifier {
|
||||
debugPrint('loading profiles: $list');
|
||||
for (final name in list) {
|
||||
final profile = await ProfileStorage.readProfileFile(name, name);
|
||||
profiles.add(MUDProfile.fromJson(jsonDecode(profile!)));
|
||||
profiles.add(MUDProfile.fromJson(profile!));
|
||||
}
|
||||
if (_currentProfile != null) {
|
||||
_currentProfile = profiles.firstWhere((e) => e.id == currentProfile.id);
|
||||
@@ -348,7 +370,7 @@ class GameStore extends ChangeNotifier {
|
||||
for (final name in list) {
|
||||
final profile = await ProfileStorage.readProfileFile(name, name);
|
||||
debugPrint('profile: $profile');
|
||||
profiles.add(MUDProfile.fromJson(jsonDecode(profile!)));
|
||||
profiles.add(MUDProfile.fromJson(profile!));
|
||||
}
|
||||
}
|
||||
debugPrint('profiles: ${profiles.map((e) => [e.name, e.password])}');
|
||||
|
||||
@@ -10,10 +10,6 @@ import 'core/store.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await getPrefs();
|
||||
// final status = await Permission.manageExternalStorage.status;
|
||||
// if (!status.isGranted) {
|
||||
// await Permission.manageExternalStorage.request();
|
||||
// }
|
||||
await FileStorage.init();
|
||||
await gameStore.init();
|
||||
if (PlatformUtils.isDesktop) {
|
||||
@@ -52,7 +48,7 @@ class MyApp extends StatelessWidget {
|
||||
builder: (context, child) {
|
||||
return GameStore.provider(child: child!);
|
||||
},
|
||||
initialRoute: '/home',
|
||||
initialRoute: Paths.home,
|
||||
routes: routes,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,10 +107,10 @@ class _AliasPageState extends State<AliasPage> {
|
||||
),
|
||||
),
|
||||
DropdownMenu(
|
||||
initialSelection: alias.action.sendTo,
|
||||
initialSelection: alias.action.target,
|
||||
onSelected: (value) {
|
||||
if (value is MUDActionTarget) {
|
||||
alias.action.sendTo = value;
|
||||
alias.action.target = value;
|
||||
}
|
||||
},
|
||||
dropdownMenuEntries: const [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../core/platform_utils.dart';
|
||||
import '../core/routes.dart';
|
||||
import '../core/store.dart';
|
||||
|
||||
@@ -20,25 +21,39 @@ class HomeScaffold extends StatelessWidget {
|
||||
builder: builder,
|
||||
),
|
||||
endDrawer: Drawer(
|
||||
child: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('Aliases'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.aliases),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Triggers'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.triggers),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Settings'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.settings),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Disconnect'),
|
||||
onTap: () => gameStore.connect(context),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: PlatformUtils.isDesktop
|
||||
? const EdgeInsets.only(top: 60)
|
||||
: EdgeInsets.zero,
|
||||
child: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('Aliases'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.aliases),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Triggers'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.triggers),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Variables'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.variables),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Settings'),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.settings),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Disconnect'),
|
||||
onTap: () async {
|
||||
await gameStore.disconnect();
|
||||
if (context.mounted) {
|
||||
gameStore.connect(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/features/profile.dart';
|
||||
import '../core/platform_utils.dart';
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
const ProfilePage({super.key, required this.profile});
|
||||
@@ -33,73 +32,99 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
width: 1200,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.name),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Profile name',
|
||||
helperText: 'The name of the profile',
|
||||
),
|
||||
onChanged: (value) {
|
||||
profile.name = value;
|
||||
},
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.name),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Profile name',
|
||||
helperText: 'The name of the profile',
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.host),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Host',
|
||||
),
|
||||
onChanged: (value) {
|
||||
profile.host = value;
|
||||
},
|
||||
onChanged: (value) {
|
||||
profile.name = value;
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.host),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Host',
|
||||
),
|
||||
TextField(
|
||||
controller:
|
||||
TextEditingController(text: profile.port.toString()),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Port',
|
||||
),
|
||||
onChanged: (value) {
|
||||
profile.port = int.tryParse(value) ?? profile.port;
|
||||
},
|
||||
onChanged: (value) {
|
||||
profile.host = value;
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller:
|
||||
TextEditingController(text: profile.port.toString()),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Port',
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Enable MCCP Compression'),
|
||||
subtitle: const Text(
|
||||
'Enables MCCP Compression for this profile (if available).',
|
||||
),
|
||||
value: profile.mccpEnabled,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
profile.mccpEnabled = value ?? false;
|
||||
});
|
||||
},
|
||||
onChanged: (value) {
|
||||
profile.port = int.tryParse(value) ?? profile.port;
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Enable MCCP Compression'),
|
||||
subtitle: const Text(
|
||||
'Enables MCCP Compression for this profile (if available).',
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.username),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Username',
|
||||
),
|
||||
onChanged: (value) {
|
||||
profile.username = value;
|
||||
},
|
||||
value: profile.mccpEnabled,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
profile.mccpEnabled = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
'Authentication',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.username),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Username',
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.password),
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Password',
|
||||
),
|
||||
onChanged: (value) {
|
||||
profile.password = value;
|
||||
},
|
||||
onChanged: (value) {
|
||||
profile.username = value;
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(text: profile.password),
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Password',
|
||||
),
|
||||
],
|
||||
)),
|
||||
onChanged: (value) {
|
||||
profile.password = value;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
DropdownMenu(
|
||||
label: const Text('Authentication Method'),
|
||||
initialSelection: profile.authMethod,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
profile.authMethod = value as AuthMethod;
|
||||
});
|
||||
},
|
||||
dropdownMenuEntries: const [
|
||||
DropdownMenuEntry(
|
||||
value: AuthMethod.none,
|
||||
label: 'None',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: AuthMethod.diku,
|
||||
label: 'Diku-style',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -107,10 +107,10 @@ class _TriggerPageState extends State<TriggerPage> {
|
||||
),
|
||||
),
|
||||
DropdownMenu(
|
||||
initialSelection: trigger.action.sendTo,
|
||||
initialSelection: trigger.action.target,
|
||||
onSelected: (value) {
|
||||
if (value is MUDActionTarget) {
|
||||
trigger.action.sendTo = value;
|
||||
trigger.action.target = value;
|
||||
}
|
||||
},
|
||||
dropdownMenuEntries: const [
|
||||
|
||||
61
lib/pages/variable_list_page.dart
Normal file
61
lib/pages/variable_list_page.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mudblock/core/store.dart';
|
||||
|
||||
import '../core/features/variable.dart';
|
||||
import '../core/routes.dart';
|
||||
|
||||
class VariableListPage extends StatelessWidget with GameStoreMixin {
|
||||
const VariableListPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Variables'),
|
||||
),
|
||||
body: GameStore.consumer(
|
||||
builder: (context, store, child) {
|
||||
debugPrint('Variable list rebuild');
|
||||
final variables = store.variables;
|
||||
return ListView.builder(
|
||||
itemCount: variables.length,
|
||||
itemBuilder: (context, item) {
|
||||
final variable = variables[item];
|
||||
return ListTile(
|
||||
key: Key(variable.name),
|
||||
title: Text(variable.name),
|
||||
subtitle: Text(variable.strValue),
|
||||
onTap: () async {
|
||||
final updated = await Navigator.pushNamed(
|
||||
context,
|
||||
Paths.variable,
|
||||
arguments: variable,
|
||||
);
|
||||
if (updated != null) {
|
||||
await save(store, updated as Variable);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
final store = storeOf(context);
|
||||
final variable = await Navigator.pushNamed(context, Paths.variable);
|
||||
if (variable != null) {
|
||||
save(store, variable as Variable);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> save(GameStore store, Variable updated) async {
|
||||
await store.currentProfile.saveVariable(store.variables, updated);
|
||||
await store.loadVariables();
|
||||
}
|
||||
}
|
||||
|
||||
93
lib/pages/variable_page.dart
Normal file
93
lib/pages/variable_page.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/features/variable.dart';
|
||||
|
||||
class VariablePage extends StatefulWidget {
|
||||
const VariablePage({super.key, required this.variable});
|
||||
|
||||
final Variable? variable;
|
||||
|
||||
@override
|
||||
State<VariablePage> createState() => _VariablePageState();
|
||||
}
|
||||
|
||||
class _VariablePageState extends State<VariablePage> {
|
||||
late final Variable variable;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
variable = widget.variable?.copyWith() ?? Variable.empty();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Variable'),
|
||||
),
|
||||
body: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
width: 1200,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: TextEditingController(text: variable.name),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
helperText: 'The name of the variable',
|
||||
),
|
||||
onChanged: (value) {
|
||||
variable.name = value;
|
||||
},
|
||||
),
|
||||
DropdownMenu(
|
||||
initialSelection: variable.type,
|
||||
onSelected: (value) {
|
||||
variable.type = value as VariableType;
|
||||
},
|
||||
dropdownMenuEntries: VariableType.values
|
||||
.map(
|
||||
(e) => DropdownMenuEntry(
|
||||
value: e,
|
||||
label: e.name,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
TextField(
|
||||
controller:
|
||||
TextEditingController(text: variable.strValue),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Value',
|
||||
helperText: 'The value of the variable',
|
||||
),
|
||||
onChanged: (value) {
|
||||
variable.strValue = value;
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, variable);
|
||||
},
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
40
pubspec.lock
40
pubspec.lock
@@ -280,46 +280,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: ad65ba9af42a3d067203641de3fd9f547ded1410bad3b84400c2b4899faede70
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.0"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: ace7d15a3d1a4a0b91c041d01e5405df221edb9de9116525efc773c74e6fc790
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.5"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.5"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+1
|
||||
version: 0.1.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.1.2 <4.0.0'
|
||||
@@ -46,7 +46,7 @@ dependencies:
|
||||
meta: ^1.9.1
|
||||
lua_dardo: ^0.0.5
|
||||
path_provider: ^2.1.1
|
||||
permission_handler: ^11.0.0
|
||||
# permission_handler: ^11.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -6,13 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||
WindowManagerPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
permission_handler_windows
|
||||
screen_retriever
|
||||
window_manager
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user