ADDTL; Edited the deserialization routine for mods to make it less prone to errors in the event a mod does not contain every field in the NBT data, ex. new comment field would have crashed the deserializer, causing it to skip past the mod or fully reset server settings.
322 lines
9.7 KiB
Dart
322 lines
9.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../structs/mod.dart';
|
|
import '../structs/settings.dart';
|
|
|
|
class ModManager extends StatefulWidget {
|
|
Settings settings;
|
|
ModManager({super.key, required this.settings});
|
|
|
|
@override
|
|
ModManagerState createState() => ModManagerState(settings: settings);
|
|
}
|
|
|
|
class ModManagerState extends State<ModManager> {
|
|
Settings settings;
|
|
ModManagerState({required this.settings});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text("Conan Exiles Server Manager - Mod Manager"),
|
|
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
|
actions: [
|
|
IconButton(
|
|
onPressed: () async {
|
|
for (Mod M in settings.inst!.mods) {
|
|
M.enabled = false;
|
|
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
|
|
settings.loggedInUser!
|
|
.sendDiscordActionLog("${M.mod_name} was disabled");
|
|
|
|
setState(() {});
|
|
}
|
|
},
|
|
icon: Icon(Icons.swipe_down)),
|
|
IconButton(
|
|
onPressed: () async {
|
|
for (Mod M in settings.inst!.mods) {
|
|
M.enabled = true;
|
|
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
|
|
settings.loggedInUser!
|
|
.sendDiscordActionLog("${M.mod_name} was enabled");
|
|
|
|
setState(() {});
|
|
}
|
|
},
|
|
icon: Icon(Icons.swipe_up))
|
|
],
|
|
),
|
|
body: ReorderableListView.builder(
|
|
onReorder: (oldIndex, newIndex) {
|
|
if (oldIndex < newIndex) {
|
|
// From top to Bottom
|
|
int end = newIndex - 1;
|
|
Mod item = settings.inst!.mods[oldIndex];
|
|
int i = 0;
|
|
int local = oldIndex;
|
|
do {
|
|
settings.inst!.mods[local] = settings.inst!.mods[++local];
|
|
i++;
|
|
} while (i < end - oldIndex);
|
|
settings.inst!.mods[end] = item;
|
|
|
|
settings.loggedInUser!.sendDiscordActionLog(
|
|
"Reordered Mod List\n\n${item.mod_name} is now in load order $end");
|
|
} else if (oldIndex > newIndex) {
|
|
//From bottom to top
|
|
Mod item = settings.inst!.mods[oldIndex];
|
|
for (int i = oldIndex; i > newIndex; i--) {
|
|
settings.inst!.mods[i] = settings.inst!.mods[i - 1];
|
|
}
|
|
settings.inst!.mods[newIndex] = item;
|
|
|
|
settings.loggedInUser!.sendDiscordActionLog(
|
|
"Reordered Mod List\n\n${item.mod_name} is now in load order $newIndex");
|
|
}
|
|
setState(() {
|
|
settings.Write();
|
|
});
|
|
},
|
|
itemBuilder: (ctx, idx) {
|
|
Mod mod = settings.inst!.mods[idx];
|
|
return Padding(
|
|
key: Key(mod.mod_instance_id()),
|
|
padding: EdgeInsets.all(12),
|
|
child: ListTile(
|
|
title: Text(mod.mod_name),
|
|
subtitle: Text(
|
|
"ID: ${mod.mod_id}\nLoad Order: $idx\nEnabled: ${mod.enabled}"),
|
|
onTap: () async {
|
|
final reply = await Navigator.pushNamed(
|
|
context, "/server/mods/edit",
|
|
arguments: Mod(
|
|
mod_id: mod.mod_id,
|
|
mod_name: mod.mod_name,
|
|
mod_pak: mod.mod_pak,
|
|
mod_hash: mod.mod_hash,
|
|
newMod: false,
|
|
enabled: mod.enabled));
|
|
|
|
if (reply != null) {
|
|
ModEditReturnArgs MERA = reply as ModEditReturnArgs;
|
|
if (MERA.delete) {
|
|
setState(() {
|
|
settings.loggedInUser!.sendDiscordActionLog(
|
|
"Deleted Mod: ${settings.inst!.mods[idx].mod_name}");
|
|
|
|
settings.inst!.mods.removeAt(idx);
|
|
});
|
|
return;
|
|
}
|
|
setState(() {
|
|
settings.inst!.mods[idx] = MERA.mod!;
|
|
|
|
settings.loggedInUser!.sendDiscordActionLog(
|
|
"Edited Mod: ${MERA.mod!.mod_name}");
|
|
});
|
|
}
|
|
},
|
|
),
|
|
);
|
|
},
|
|
itemCount: settings.inst!.mods.length,
|
|
),
|
|
floatingActionButton: ElevatedButton(
|
|
child: Icon(Icons.add),
|
|
onPressed: () async {
|
|
// Open new mod info screen
|
|
final reply = await Navigator.pushNamed(context, "/server/mods/edit",
|
|
arguments: Mod(newMod: true));
|
|
|
|
if (reply != null) {
|
|
ModEditReturnArgs MERA = reply as ModEditReturnArgs;
|
|
|
|
setState(() {
|
|
settings.inst!.mods.add(MERA.mod!);
|
|
});
|
|
|
|
settings.loggedInUser!
|
|
.sendDiscordActionLog("Added Mod: ${MERA.mod!.mod_name}");
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ModPage extends StatefulWidget {
|
|
@override
|
|
State<StatefulWidget> createState() => ModPageState();
|
|
}
|
|
|
|
class ModPageState extends State<ModPage> {
|
|
TextEditingController id = TextEditingController();
|
|
TextEditingController name = TextEditingController();
|
|
String instance = "";
|
|
bool isNewMod = false;
|
|
String pak = "Not initialized";
|
|
String hash = "";
|
|
bool enabled = false;
|
|
TextEditingController comment = TextEditingController();
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
final args = ModalRoute.of(context)!.settings.arguments as Mod?;
|
|
|
|
if (args != null) {
|
|
id.text = args.mod_id.toString();
|
|
name.text = args.mod_name;
|
|
isNewMod = args.newMod;
|
|
instance = args.mod_instance_id();
|
|
pak = args.mod_pak;
|
|
hash = args.mod_hash;
|
|
enabled = args.enabled;
|
|
comment.text = args.comment;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text("Mod Editor"),
|
|
backgroundColor: Color.fromARGB(255, 100, 0, 0),
|
|
),
|
|
floatingActionButton: ElevatedButton(
|
|
child: Text("Save"),
|
|
onPressed: () {
|
|
int idVal = 0;
|
|
try {
|
|
idVal = int.parse(id.text);
|
|
} catch (E) {}
|
|
|
|
Navigator.pop(
|
|
context,
|
|
ModEditReturnArgs(
|
|
delete: false,
|
|
mod: Mod(
|
|
mod_id: idVal,
|
|
mod_name: name.text,
|
|
newMod: false,
|
|
enabled: enabled,
|
|
comment: comment.text)));
|
|
},
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(children: [
|
|
Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 150,
|
|
child: ListTile(
|
|
leading: Icon(Icons.abc_rounded),
|
|
title: Text("Mod Name"),
|
|
)),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: name,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(4)))),
|
|
)
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 150,
|
|
child: ListTile(
|
|
leading: Icon(Icons.perm_identity), title: Text("Mod ID")),
|
|
),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: id,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(4))),
|
|
))
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 16,
|
|
),
|
|
ListTile(
|
|
title: Text("Mod Instance ID"),
|
|
subtitle: Text(instance),
|
|
),
|
|
ListTile(
|
|
title: Text("Mod Pak File: $pak"),
|
|
subtitle: Text("Mod pak file name as detected during downloading"),
|
|
),
|
|
ListTile(
|
|
title: Text("Mod Hash"),
|
|
subtitle: Text(hash),
|
|
),
|
|
SwitchListTile(
|
|
value: enabled,
|
|
onChanged: (V) {
|
|
setState(() {
|
|
enabled = V;
|
|
});
|
|
|
|
Settings.Instance.loggedInUser!.sendDiscordActionLog(
|
|
"${name.text} was ${enabled ? "enabled" : "disabled"}");
|
|
},
|
|
title: Text("Enabled"),
|
|
subtitle: Text("Whether mod is enabled or not"),
|
|
),
|
|
Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 150,
|
|
child: ListTile(
|
|
leading: Icon(Icons.comment),
|
|
title: Text("Comment"),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: comment,
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8))),
|
|
))
|
|
],
|
|
),
|
|
if (!isNewMod)
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
Navigator.pop(context, ModEditReturnArgs(delete: true));
|
|
},
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.delete),
|
|
SizedBox(
|
|
width: 4,
|
|
),
|
|
Text("Remove Mod")
|
|
],
|
|
)),
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ModEditReturnArgs {
|
|
bool delete;
|
|
Mod? mod;
|
|
|
|
ModEditReturnArgs({required this.delete, this.mod});
|
|
}
|