From 3c1e949b0da3979abfd857844f1c76fb0349e972 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sat, 15 Mar 2025 23:43:02 +0100 Subject: [PATCH] Fix up the display a little Cleanup crew --- lib/main.dart | 361 ++++++++++++++++++++++----------------------- lib/modloader.dart | 24 +-- 2 files changed, 190 insertions(+), 195 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 6ddd6b0..41a5abc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,7 @@ late ModList modManager; void main() { // Initialize the mod manager modManager = ModList(path: modsRoot); - + // Start the app runApp(const RimWorldModManager()); } @@ -95,7 +95,8 @@ class _ModListPageState extends State { bool _isLoading = false; String _loadingStatus = ''; int _totalModsFound = 0; - bool _skipFileCount = false; // Skip file counting by default for faster loading + bool _skipFileCount = + false; // Skip file counting by default for faster loading @override void initState() { @@ -119,9 +120,10 @@ class _ModListPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: _loadedMods.isEmpty && !_isLoading - ? _buildEmptyState() - : _buildModList(), + body: + _loadedMods.isEmpty && !_isLoading + ? _buildEmptyState() + : _buildModList(), ); } @@ -198,7 +200,10 @@ class _ModListPageState extends State { itemBuilder: (context, index) { final mod = _loadedMods[index]; return Card( - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), child: ListTile( title: Text(mod.name), subtitle: Text( @@ -211,12 +216,20 @@ class _ModListPageState extends State { if (mod.isBaseGame) Tooltip( message: 'Base Game', - child: Icon(Icons.home, color: Colors.blue, size: 16), + child: Icon( + Icons.home, + color: Colors.blue, + size: 16, + ), ), if (mod.isExpansion) Tooltip( message: 'Expansion', - child: Icon(Icons.star, color: Colors.yellow, size: 16), + child: Icon( + Icons.star, + color: Colors.yellow, + size: 16, + ), ), Icon( Icons.circle, @@ -262,22 +275,25 @@ class _ModListPageState extends State { }); // Use the simplified loading approach - modManager.loadWithConfig(skipFileCount: _skipFileCount).then((_) { - setState(() { - _loadedMods = modManager.mods.values.toList(); - _isLoading = false; - _loadingStatus = modManager.loadingStatus; - _totalModsFound = modManager.totalModsFound; - - // Sort mods by name for better display - _loadedMods.sort((a, b) => a.name.compareTo(b.name)); - }); - }).catchError((error) { - setState(() { - _isLoading = false; - _loadingStatus = 'Error loading mods: $error'; - }); - }); + modManager + .loadWithConfig(skipFileCount: _skipFileCount) + .then((_) { + setState(() { + _loadedMods = modManager.mods.values.toList(); + _isLoading = false; + _loadingStatus = modManager.loadingStatus; + _totalModsFound = modManager.totalModsFound; + + // Sort mods by name for better display + _loadedMods.sort((a, b) => a.name.compareTo(b.name)); + }); + }) + .catchError((error) { + setState(() { + _isLoading = false; + _loadingStatus = 'Error loading mods: $error'; + }); + }); } } @@ -342,7 +358,8 @@ class _LoadOrderPageState extends State { ), const SizedBox(width: 16), ElevatedButton( - onPressed: _isLoading || _sortedMods.isEmpty ? null : _saveModOrder, + onPressed: + _isLoading || _sortedMods.isEmpty ? null : _saveModOrder, child: const Text('Save Load Order'), ), ], @@ -360,9 +377,10 @@ class _LoadOrderPageState extends State { _statusMessage, textAlign: TextAlign.center, style: TextStyle( - color: _hasCycles || _incompatibleMods.isNotEmpty - ? Colors.orange - : Colors.green, + color: + _hasCycles || _incompatibleMods.isNotEmpty + ? Colors.orange + : Colors.green, ), ), ], @@ -375,13 +393,14 @@ class _LoadOrderPageState extends State { child: Text( _statusMessage, style: TextStyle( - color: _hasCycles || _incompatibleMods.isNotEmpty - ? Colors.orange - : Colors.green, + color: + _hasCycles || _incompatibleMods.isNotEmpty + ? Colors.orange + : Colors.green, ), ), ), - if (_hasCycles && _cycleInfo != null) + if (_hasCycles && _cycleInfo != null) Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( @@ -399,164 +418,138 @@ class _LoadOrderPageState extends State { 'Incompatible mods detected:', style: TextStyle(color: Colors.orange), ), - ...List.generate(_incompatibleMods.length > 5 ? 5 : _incompatibleMods.length, (index) { - final pair = _incompatibleMods[index]; - return Text( - '- ${modManager.mods[pair[0]]?.name} and ${modManager.mods[pair[1]]?.name}', - style: TextStyle(color: Colors.orange), - ); - }), + ...List.generate( + _incompatibleMods.length > 5 + ? 5 + : _incompatibleMods.length, + (index) { + final pair = _incompatibleMods[index]; + return Text( + '- ${modManager.mods[pair[0]]?.name} and ${modManager.mods[pair[1]]?.name}', + style: TextStyle(color: Colors.orange), + ); + }, + ), if (_incompatibleMods.length > 5) Text('...and ${_incompatibleMods.length - 5} more'), ], ), ), const SizedBox(height: 16), - if (_sortedMods.isNotEmpty) + if (_sortedMods.isNotEmpty) Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text('Legend:', style: TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 4), - Row( - children: [ - Icon(Icons.home, color: Colors.blue, size: 16), - const SizedBox(width: 4), - Text('Base Game', style: TextStyle(fontSize: 12)), - const SizedBox(width: 12), - Icon(Icons.star, color: Colors.yellow, size: 16), - const SizedBox(width: 4), - Text('Expansion', style: TextStyle(fontSize: 12)), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Icon(Icons.link, color: Colors.orange, size: 16), - const SizedBox(width: 4), - Text('Hard dependencies', style: TextStyle(fontSize: 12)), - const SizedBox(width: 12), - Icon(Icons.link_off, color: Colors.blue, size: 16), - const SizedBox(width: 4), - Text('Soft dependencies', style: TextStyle(fontSize: 12)), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Icon(Icons.arrow_forward, color: Colors.green, size: 16), - const SizedBox(width: 4), - Text('Loads before other mods', style: TextStyle(fontSize: 12)), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Container(width: 12, height: 12, color: Colors.amber), - const SizedBox(width: 4), - Text('Very large mod (>1000 files)', style: TextStyle(fontSize: 12)), - const SizedBox(width: 12), - Container(width: 12, height: 12, color: Colors.yellow.shade600), - const SizedBox(width: 4), - Text('Large mod (>500 files)', style: TextStyle(fontSize: 12)), - ], - ), - ], - ), ), Expanded( - child: _sortedMods.isEmpty - ? Center( - child: Text( - modManager.modsLoaded - ? 'Click "Auto-sort Mods" to generate a load order for ${modManager.mods.length} loaded mods.' - : 'Please go to the Mods tab first to load mods.', - textAlign: TextAlign.center, + child: + _sortedMods.isEmpty + ? Center( + child: Text( + modManager.modsLoaded + ? 'Click "Auto-sort Mods" to generate a load order for ${modManager.mods.length} loaded mods.' + : 'Please go to the Mods tab first to load mods.', + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + itemCount: _sortedMods.length, + itemBuilder: (context, index) { + final mod = _sortedMods[index]; + return Card( + margin: const EdgeInsets.symmetric(vertical: 4), + child: ListTile( + leading: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('${index + 1}'), + const SizedBox(height: 2), + if (mod.size > 0) + Tooltip( + message: 'This mod contains ${mod.size} files.', + child: Text( + '${mod.size}', + style: TextStyle( + fontSize: 10, + color: Colors.grey, + ), + ), + ), + ], + ), + title: Text(mod.name), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(mod.id, style: TextStyle(fontSize: 12)), + ], + ), + isThreeLine: + mod.hardDependencies.isNotEmpty || + mod.loadAfter.isNotEmpty || + mod.loadBefore.isNotEmpty, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (mod.isBaseGame) + Tooltip( + message: 'Base Game', + child: Icon( + Icons.home, + color: Colors.blue, + size: 24, + ), + ), + if (mod.isExpansion) + Tooltip( + message: 'Expansion', + child: Icon( + Icons.star, + color: Colors.yellow, + size: 24, + ), + ), + if (mod.hardDependencies.isNotEmpty) + Tooltip( + message: + 'Hard dependencies:\n${mod.hardDependencies.join('\n')}', + child: Icon( + Icons.link, + color: Colors.orange, + size: 24, + ), + ), + const SizedBox(width: 4), + if (mod.loadAfter.isNotEmpty) + Tooltip( + message: + 'Loads after other mods:\n${mod.loadAfter.join('\n')}', + child: Icon( + Icons.arrow_back, + color: Colors.blue, + size: 24, + ), + ), + const SizedBox(width: 4), + if (mod.loadBefore.isNotEmpty) + Tooltip( + message: + 'Loads before other mods:\n${mod.loadBefore.join('\n')}', + child: Icon( + Icons.arrow_forward, + color: Colors.green, + size: 24, + ), + ), + ], + ), + ), + ); + }, ), - ) - : ListView.builder( - itemCount: _sortedMods.length, - itemBuilder: (context, index) { - final mod = _sortedMods[index]; - return Card( - margin: const EdgeInsets.symmetric(vertical: 4), - child: ListTile( - leading: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('${index + 1}'), - const SizedBox(height: 2), - if (mod.size > 0) Text('${mod.size}', style: TextStyle(fontSize: 10, color: Colors.grey)), - ], - ), - title: Text(mod.name), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(mod.id, style: TextStyle(fontSize: 10)), - if (mod.hardDependencies.isNotEmpty) - Text( - 'Hard dependencies: ${mod.hardDependencies.length}', - style: TextStyle(fontSize: 10, color: Colors.orange), - ), - if (mod.softDependencies.isNotEmpty) - Text( - 'Soft dependencies: ${mod.softDependencies.length}', - style: TextStyle(fontSize: 10, color: Colors.blue), - ), - ], - ), - isThreeLine: mod.hardDependencies.isNotEmpty || mod.softDependencies.isNotEmpty, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (mod.isBaseGame) - Tooltip( - message: 'Base Game', - child: Icon(Icons.home, color: Colors.blue, size: 16), - ), - if (mod.isExpansion) - Tooltip( - message: 'Expansion', - child: Icon(Icons.star, color: Colors.yellow, size: 16), - ), - if (mod.hardDependencies.isNotEmpty) - Icon(Icons.link, color: Colors.orange, size: 16), - const SizedBox(width: 4), - if (mod.softDependencies.isNotEmpty) - Icon(Icons.link_off, color: Colors.blue, size: 16), - const SizedBox(width: 4), - if (mod.loadBefore.isNotEmpty) - Tooltip( - message: 'Loads before other mods', - child: Icon(Icons.arrow_forward, color: Colors.green, size: 16), - ), - const SizedBox(width: 4), - Text( - '${mod.size} files', - style: TextStyle( - fontSize: 12, - fontWeight: mod.size > 1000 ? FontWeight.bold : FontWeight.normal, - color: mod.size > 1000 - ? Colors.amber - : mod.size > 500 - ? Colors.yellow.shade600 - : Colors.grey, - ), - ), - ], - ), - ), - ); - }, - ), ), ], ), @@ -567,7 +560,8 @@ class _LoadOrderPageState extends State { void _sortMods() async { if (modManager.mods.isEmpty) { setState(() { - _statusMessage = 'No mods have been loaded yet. Please go to the Mods tab and load mods first.'; + _statusMessage = + 'No mods have been loaded yet. Please go to the Mods tab and load mods first.'; }); return; } @@ -587,14 +581,14 @@ class _LoadOrderPageState extends State { // Check for cycles first final hardGraph = modManager.buildDependencyGraph(); final cycle = modManager.detectCycle(hardGraph); - + if (cycle != null) { setState(() { _hasCycles = true; _cycleInfo = cycle; }); } - + // Get incompatibilities _incompatibleMods = modManager.findIncompatibilities(); @@ -609,7 +603,8 @@ class _LoadOrderPageState extends State { _statusMessage += ' Warning: Dependency cycles were found and fixed.'; } if (_incompatibleMods.isNotEmpty) { - _statusMessage += ' Warning: ${_incompatibleMods.length} incompatible mod pairs found.'; + _statusMessage += + ' Warning: ${_incompatibleMods.length} incompatible mod pairs found.'; } }); } catch (e) { @@ -631,20 +626,20 @@ class _LoadOrderPageState extends State { try { // Create a ConfigFile instance final configFile = ConfigFile(path: configPath); - + // Load the current config configFile.load(); - + // Replace the mods with our sorted list // We need to convert our Mods to the format expected by the config file configFile.mods.clear(); for (final mod in _sortedMods) { configFile.mods.add(mod); } - + // Save the updated config configFile.save(); - + setState(() { _isLoading = false; _statusMessage = 'Mod load order saved successfully!'; diff --git a/lib/modloader.dart b/lib/modloader.dart index 8206971..dd04f3c 100644 --- a/lib/modloader.dart +++ b/lib/modloader.dart @@ -27,7 +27,7 @@ class Mod { final List versions; // ModMetaData.supportedVersions final String description; // ModMetaData.description final List hardDependencies; // ModMetaData.modDependencies - final List softDependencies; // ModMetaData.loadAfter + final List loadAfter; // ModMetaData.loadAfter final List loadBefore; // ModMetaData.loadBefore final List incompatabilities; // ModMetaData.incompatibleWith final bool enabled; // ConfigFile.mods.firstWhere((mod) => mod.id == id).enabled @@ -42,7 +42,7 @@ class Mod { required this.versions, required this.description, required this.hardDependencies, - required this.softDependencies, + required this.loadAfter, required this.loadBefore, required this.incompatabilities, required this.enabled, @@ -127,9 +127,9 @@ class Mod { // Silent error for optional element } - List softDependencies = []; + List loadAfter = []; try { - softDependencies = + loadAfter = metadata .findElements('loadAfter') .first @@ -188,8 +188,8 @@ class Mod { bool isExpansion = !isBaseGame && id.startsWith('ludeon.rimworld.'); // If this is an expansion, ensure it depends on the base game - if (isExpansion && !softDependencies.contains('ludeon.rimworld')) { - softDependencies.add('ludeon.rimworld'); + if (isExpansion && !loadAfter.contains('ludeon.rimworld')) { + loadAfter.add('ludeon.rimworld'); } final fileCountTime = @@ -208,7 +208,7 @@ class Mod { versions: versions, description: description, hardDependencies: hardDependencies, - softDependencies: softDependencies, + loadAfter: loadAfter, loadBefore: loadBefore, incompatabilities: incompatabilities, enabled: false, @@ -267,7 +267,7 @@ class ModList { description: isBaseGame ? "RimWorld base game" : isExpansion ? "RimWorld expansion" : "", hardDependencies: [], - softDependencies: isExpansion ? ['ludeon.rimworld'] : [], + loadAfter: isExpansion ? ['ludeon.rimworld'] : [], loadBefore: [], incompatabilities: [], enabled: true, @@ -319,7 +319,7 @@ class ModList { versions: mod.versions, description: mod.description, hardDependencies: mod.hardDependencies, - softDependencies: mod.softDependencies, + loadAfter: mod.loadAfter, loadBefore: mod.loadBefore, incompatabilities: mod.incompatabilities, enabled: activeModIds.contains(mod.id), // Set enabled based on config @@ -336,7 +336,7 @@ class ModList { versions: mod.versions, description: mod.description, hardDependencies: mod.hardDependencies, - softDependencies: mod.softDependencies, + loadAfter: mod.loadAfter, loadBefore: mod.loadBefore, incompatabilities: mod.incompatabilities, enabled: activeModIds.contains(mod.id), // Set enabled based on config @@ -427,7 +427,7 @@ class ModList { // Add soft dependencies (loadAfter) for (final mod in mods.values) { - for (final dependency in mod.softDependencies) { + for (final dependency in mod.loadAfter) { // Only add if the dependency exists in our loaded mods if (mods.containsKey(dependency)) { graph[mod.id]!.add(dependency); @@ -738,7 +738,7 @@ class ConfigFile { description: isBaseGame ? "RimWorld base game" : isExpansion ? "RimWorld expansion" : "", hardDependencies: [], - softDependencies: isExpansion ? ['ludeon.rimworld'] : [], + loadAfter: isExpansion ? ['ludeon.rimworld'] : [], loadBefore: [], incompatabilities: [], enabled: true,