From 4c768a7fd425487e45f10ab00c7cfb09123fc717 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Tue, 18 Mar 2025 00:31:32 +0100 Subject: [PATCH] Remove main again --- lib/main.dart | 2603 +++++++++++++++++++++++++------------------------ 1 file changed, 1302 insertions(+), 1301 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d1f7a2b..5bc42cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,1301 +1,1302 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:rimworld_modman/logger.dart'; -import 'package:rimworld_modman/mod.dart'; -import 'package:rimworld_modman/mod_list.dart'; - -// Constants for file paths -final String root = - Platform.isWindows - ? r'C:/Users/Administrator/Seafile/Games-RimWorld' - : '~/Library/Application Support/RimWorld'; -final String modsRoot = Platform.isWindows ? '$root/294100' : '$root/Mods'; -final String configRoot = - Platform.isWindows - ? '$root/AppData/RimWorld by Ludeon Studios/Config' - : '$root/Config'; -final String configPath = '$configRoot/ModsConfig.xml'; -final String logsPath = '$root/ModManager'; - -late ModList modManager; - -void main() { - WidgetsFlutterBinding.ensureInitialized(); - - // Get a reference to the logger (now auto-initializes) - final logger = Logger.instance; - logger.info('RimWorld Mod Manager starting...'); - - // Initialize the mod manager - modManager = ModList(modsPath: modsRoot, configPath: configPath); - - // Start the app - runApp(const RimWorldModManager()); -} - -class RimWorldModManager extends StatelessWidget { - const RimWorldModManager({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'RimWorld Mod Manager', - theme: ThemeData.dark().copyWith( - primaryColor: const Color(0xFF3D4A59), - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF3D4A59), - brightness: Brightness.dark, - ), - scaffoldBackgroundColor: const Color(0xFF1E262F), - cardColor: const Color(0xFF2A3440), - appBarTheme: const AppBarTheme( - backgroundColor: Color(0xFF2A3440), - foregroundColor: Colors.white, - ), - ), - home: const ModManagerHomePage(), - ); - } -} - -class ModManagerHomePage extends StatefulWidget { - const ModManagerHomePage({super.key}); - - @override - State createState() => _ModManagerHomePageState(); -} - -class _ModManagerHomePageState extends State { - int _selectedIndex = 0; - - final List _pages = [ - const ModManagerPage(), - const TroubleshootingPage(), - ]; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('RimWorld Mod Manager')), - body: _pages[_selectedIndex], - bottomNavigationBar: BottomNavigationBar( - currentIndex: _selectedIndex, - onTap: (index) { - setState(() { - _selectedIndex = index; - }); - }, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.extension), label: 'Mods'), - BottomNavigationBarItem( - icon: Icon(Icons.build), - label: 'Troubleshoot', - ), - ], - ), - ); - } -} - -// Combined page for mod management with two-panel layout -class ModManagerPage extends StatefulWidget { - const ModManagerPage({super.key}); - - @override - State createState() => _ModManagerPageState(); -} - -class _ModManagerPageState extends State { - // For all available mods (left panel) - List _availableMods = []; - - // For active mods (right panel) - List _activeMods = []; - - bool _isLoading = false; - String _statusMessage = ''; - int _totalModsFound = 0; - bool _skipFileCount = false; - bool _hasCycles = false; - List? _cycleInfo; - List> _incompatibleMods = []; - List? _loadOrderErrors; - - final TextEditingController _searchController = TextEditingController(); - String _searchQuery = ''; - - @override - void initState() { - super.initState(); - // Check if mods are already loaded - if (modManager.mods.isNotEmpty) { - _loadModsFromGlobalState(); - } - - _searchController.addListener(() { - setState(() { - _searchQuery = _searchController.text.toLowerCase(); - }); - }); - } - - @override - void dispose() { - _searchController.dispose(); - super.dispose(); - } - - void _loadModsFromGlobalState() { - setState(() { - // Get all mods for the left panel (sorted alphabetically) - _availableMods = modManager.mods.values.toList(); - _availableMods.sort((a, b) => a.name.compareTo(b.name)); - - // Get active mods for the right panel (in load order) - _activeMods = modManager.mods.values.where((m) => m.enabled).toList(); - _isLoading = false; - _statusMessage = 'Loaded ${_availableMods.length} mods'; - _totalModsFound = _availableMods.length; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: - _isLoading && _availableMods.isEmpty - ? _buildLoadingView() - : _availableMods.isEmpty - ? _buildEmptyState() - : _buildSplitView(), - ); - } - - Widget _buildLoadingView() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - const SizedBox(height: 16), - Text( - _statusMessage, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - ], - ), - ); - } - - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.extension, size: 64), - const SizedBox(height: 16), - Text( - 'Mod Manager', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 16), - Text( - 'Ready to scan for RimWorld mods.', - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(height: 12), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - value: _skipFileCount, - onChanged: (value) { - setState(() { - _skipFileCount = value ?? true; - }); - }, - ), - const Text('Skip file counting (faster loading)'), - ], - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: _startLoadingMods, - child: const Text('Scan for Mods'), - ), - ], - ), - ); - } - - Widget _buildSplitView() { - // Filter both available and active mods based on search query - final filteredAvailableMods = - _searchQuery.isEmpty - ? _availableMods - : _availableMods - .where( - (mod) => - mod.name.toLowerCase().contains(_searchQuery) || - mod.id.toLowerCase().contains(_searchQuery), - ) - .toList(); - - final filteredActiveMods = - _searchQuery.isEmpty - ? _activeMods - : _activeMods - .where( - (mod) => - mod.name.toLowerCase().contains(_searchQuery) || - mod.id.toLowerCase().contains(_searchQuery), - ) - .toList(); - - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - // Search field - Expanded( - child: TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: 'Search mods by name or ID...', - prefixIcon: const Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - ), - suffixIcon: - _searchQuery.isNotEmpty - ? IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - _searchController.clear(); - }, - ) - : null, - ), - ), - ), - const SizedBox(width: 8), - // Reload button - IconButton( - icon: const Icon(Icons.refresh), - tooltip: 'Reload mods', - onPressed: _startLoadingMods, - ), - const SizedBox(width: 8), - // Load Dependencies button - Tooltip( - message: - 'Automatically load missing dependencies for active mods', - child: ElevatedButton.icon( - icon: const Icon(Icons.download), - label: const Text('Load Deps'), - onPressed: _loadRequiredDependencies, - ), - ), - const SizedBox(width: 8), - // Auto-sort button - ElevatedButton.icon( - icon: const Icon(Icons.sort), - label: const Text('Auto-Sort'), - onPressed: _sortActiveMods, - ), - const SizedBox(width: 8), - // Save button - ElevatedButton.icon( - icon: const Icon(Icons.save), - label: const Text('Save'), - onPressed: _saveModOrder, - ), - ], - ), - ), - - // Status message - if (!_isLoading && _statusMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), - child: Text( - _statusMessage, - style: TextStyle( - color: - _hasCycles || _incompatibleMods.isNotEmpty - ? Colors.orange - : Colors.green, - ), - ), - ), - - // Error display section - if (_hasCycles || - _incompatibleMods.isNotEmpty || - (_loadOrderErrors?.isNotEmpty ?? false)) - Container( - margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - color: Colors.red.shade900.withOpacity(0.3), - borderRadius: BorderRadius.circular(4.0), - border: Border.all(color: Colors.red.shade800), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Cycle warnings - if (_hasCycles && _cycleInfo != null) - Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: Row( - children: [ - const Icon(Icons.loop, color: Colors.orange, size: 16), - const SizedBox(width: 4), - Expanded( - child: Text( - 'Dependency cycle detected: ${_cycleInfo!.join(" -> ")}', - style: const TextStyle(color: Colors.orange), - ), - ), - ], - ), - ), - - // Incompatible mod warnings - if (_incompatibleMods.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon( - Icons.warning, - color: Colors.orange, - size: 16, - ), - const SizedBox(width: 4), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${_incompatibleMods.length} incompatible mod pairs:', - style: const TextStyle(color: Colors.orange), - ), - if (_incompatibleMods.length <= 3) - ...List.generate(_incompatibleMods.length, ( - index, - ) { - final pair = _incompatibleMods[index]; - final mod1 = - modManager.mods[pair[0]]?.name ?? pair[0]; - final mod2 = - modManager.mods[pair[1]]?.name ?? pair[1]; - return Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 2.0, - ), - child: Text( - '• $mod1 ↔ $mod2', - style: TextStyle( - color: Colors.orange.shade300, - fontSize: 12, - ), - ), - ); - }), - ], - ), - ), - ], - ), - ), - - // Other errors (missing dependencies, etc.) - if (_loadOrderErrors?.isNotEmpty ?? false) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon( - Icons.error_outline, - color: Colors.red, - size: 16, - ), - const SizedBox(width: 4), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Dependency errors:', - style: TextStyle(color: Colors.red), - ), - ...List.generate( - _loadOrderErrors!.length > 5 - ? 5 - : _loadOrderErrors!.length, - (index) => Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 2.0, - ), - child: Text( - '• ${_loadOrderErrors![index]}', - style: TextStyle( - color: Colors.red.shade300, - fontSize: 12, - ), - ), - ), - ), - if (_loadOrderErrors!.length > 5) - Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 4.0, - ), - child: Text( - '(${_loadOrderErrors!.length - 5} more errors...)', - style: TextStyle( - color: Colors.red.shade300, - fontSize: 12, - fontStyle: FontStyle.italic, - ), - ), - ), - ], - ), - ), - ], - ), - ], - ), - ), - - // Main split view - Expanded( - child: Row( - children: [ - // LEFT PANEL - All available mods (alphabetical) - Expanded( - child: Card( - margin: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - color: Theme.of(context).primaryColor, - padding: const EdgeInsets.all(8.0), - child: Text( - 'Available Mods (${filteredAvailableMods.length}${_searchQuery.isNotEmpty ? '/${_availableMods.length}' : ''})', - style: const TextStyle(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - ), - if (_searchQuery.isNotEmpty) - Padding( - padding: const EdgeInsets.all(4.0), - child: Text( - 'Searching: "$_searchQuery"', - style: TextStyle( - fontSize: 12, - fontStyle: FontStyle.italic, - color: Colors.grey.shade400, - ), - textAlign: TextAlign.center, - ), - ), - Expanded( - child: ListView.builder( - itemCount: filteredAvailableMods.length, - itemBuilder: (context, index) { - final mod = filteredAvailableMods[index]; - final isActive = mod.enabled; - - return GestureDetector( - onDoubleTap: () => _toggleModActive(mod), - child: ListTile( - title: Text( - mod.name, - style: TextStyle( - color: - isActive ? Colors.green : Colors.white, - ), - ), - subtitle: Text( - 'ID: ${mod.id}\nSize: ${mod.size} files', - style: Theme.of(context).textTheme.bodySmall, - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (mod.isBaseGame) - const Tooltip( - message: 'Base Game', - child: Icon( - Icons.home, - color: Colors.blue, - size: 24, - ), - ), - if (mod.isExpansion) - const Tooltip( - message: 'Expansion', - child: Icon( - Icons.star, - color: Colors.yellow, - size: 24, - ), - ), - const SizedBox(width: 4), - if (mod.dependencies.isNotEmpty) - Tooltip( - message: - 'Dependencies:\n${mod.dependencies.join('\n')}', - child: const Icon( - Icons.link, - color: Colors.orange, - size: 24, - ), - ), - if (mod.loadAfter.isNotEmpty) - Tooltip( - message: - 'Loads after:\n${mod.loadAfter.join('\n')}', - child: const Icon( - Icons.arrow_downward, - color: Colors.blue, - size: 24, - ), - ), - if (mod.loadBefore.isNotEmpty) - Tooltip( - message: - 'Loads before:\n${mod.loadBefore.join('\n')}', - child: const Icon( - Icons.arrow_upward, - color: Colors.green, - size: 24, - ), - ), - ], - ), - onTap: () { - // Show mod details in future - }, - ), - ); - }, - ), - ), - ], - ), - ), - ), - - // RIGHT PANEL - Active mods (load order) - Expanded( - child: Card( - margin: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Container( - color: Theme.of(context).primaryColor, - padding: const EdgeInsets.all(8.0), - child: Text( - 'Active Mods (${filteredActiveMods.length}${_searchQuery.isNotEmpty ? '/${_activeMods.length}' : ''})', - style: const TextStyle(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - _searchQuery.isNotEmpty - ? 'Searching: "$_searchQuery"' - : 'Larger mods are prioritized during auto-sorting.', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade400, - fontStyle: FontStyle.italic, - ), - textAlign: TextAlign.center, - ), - ), - Expanded( - child: ListView.builder( - itemCount: filteredActiveMods.length, - itemBuilder: (context, index) { - final mod = filteredActiveMods[index]; - // Find the actual position in the complete active mods list (for correct load order) - final actualLoadOrderPosition = - _activeMods.indexWhere((m) => m.id == mod.id) + - 1; - - return GestureDetector( - onDoubleTap: () { - // Don't allow deactivating base game or expansions - if (!mod.isBaseGame && !mod.isExpansion) { - _toggleModActive(mod); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Core game and expansions cannot be deactivated', - ), - backgroundColor: Colors.orange, - ), - ); - } - }, - child: Card( - margin: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, - ), - child: ListTile( - leading: SizedBox( - width: 50, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - SizedBox( - height: 24, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 4, - ), - decoration: BoxDecoration( - color: - _searchQuery.isNotEmpty - ? Colors.blue.withOpacity( - 0.2, - ) - : null, - borderRadius: - BorderRadius.circular(4), - ), - child: Tooltip( - message: - 'Load position: $actualLoadOrderPosition of ${_activeMods.length}${_searchQuery.isNotEmpty ? " (preserved when filtering)" : ""}', - child: Text( - '$actualLoadOrderPosition', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - color: - _searchQuery.isNotEmpty - ? Colors.blue.shade300 - : null, - ), - ), - ), - ), - ), - SizedBox( - height: 24, - child: Center( - child: - mod.size > 0 - ? Tooltip( - message: - 'This mod contains ${mod.size} files.', - child: Text( - '${mod.size}', - style: const TextStyle( - fontSize: 10, - color: Colors.grey, - ), - ), - ) - : const SizedBox(), - ), - ), - ], - ), - ), - title: Text(mod.name), - subtitle: Text( - mod.id, - style: const TextStyle(fontSize: 12), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (mod.isBaseGame) - const Tooltip( - message: 'Base Game', - child: Icon( - Icons.home, - color: Colors.blue, - size: 24, - ), - ), - if (mod.isExpansion) - const Tooltip( - message: 'Expansion', - child: Icon( - Icons.star, - color: Colors.yellow, - size: 24, - ), - ), - if (mod.dependencies.isNotEmpty) - Tooltip( - message: - 'Dependencies:\n${mod.dependencies.join('\n')}', - child: const 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: const Icon( - Icons.arrow_downward, - 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: const Icon( - Icons.arrow_upward, - color: Colors.green, - size: 24, - ), - ), - ], - ), - onTap: () { - // Show mod details in future - }, - ), - ), - ); - }, - ), - ), - ], - ), - ), - ), - ], - ), - ), - ], - ); - } - - void _startLoadingMods() { - setState(() { - _availableMods.clear(); - _activeMods.clear(); - _isLoading = true; - _statusMessage = 'Scanning for mods...'; - _hasCycles = false; - _cycleInfo = null; - _incompatibleMods = []; - }); - - // Create an async function to load mods - Future loadMods() async { - try { - // First load available mods - await for (final mod in modManager.loadAvailable()) { - // Update UI for each mod loaded - if (mounted) { - setState(() { - _statusMessage = 'Loaded mod: ${mod.name}'; - _totalModsFound = modManager.mods.length; - }); - } - } - - // Then load active mods from config - await for (final mod in modManager.loadActive()) { - // Update UI as active mods are loaded - if (mounted) { - setState(() { - _statusMessage = 'Loading active mod: ${mod.name}'; - }); - } - } - - // Update the UI with all loaded mods - if (mounted) { - _loadModsFromGlobalState(); - setState(() { - _statusMessage = - 'Loaded ${_availableMods.length} mods, ${_activeMods.length} active'; - }); - } - } catch (error) { - if (mounted) { - setState(() { - _isLoading = false; - _statusMessage = 'Error loading mods: $error'; - }); - } - } - } - - // Start the loading process - loadMods(); - } - - void _toggleModActive(Mod mod) { - // Cannot deactivate base game or expansions - if ((mod.isBaseGame || mod.isExpansion) && mod.enabled) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Core game and expansions cannot be deactivated'), - backgroundColor: Colors.orange, - ), - ); - return; - } - - // Get the current state before toggling - final bool wasEnabled = mod.enabled; - - // Toggle the mod in the global mod manager - modManager.setEnabled(mod.id, !wasEnabled); - Logger.instance.info( - 'Toggled mod ${mod.name} (${mod.id}) from ${wasEnabled ? 'enabled' : 'disabled'} to ${!wasEnabled ? 'enabled' : 'disabled'}', - ); - - // Update the UI - setState(() { - // Update in the available mods list - final index = _availableMods.indexWhere((m) => m.id == mod.id); - if (index >= 0) { - _availableMods[index] = modManager.mods[mod.id]!; - } - - // Update the active mods list - _activeMods = modManager.mods.values.where((m) => m.enabled).toList(); - - _statusMessage = - 'Mod "${mod.name}" ${!wasEnabled ? 'activated' : 'deactivated'}'; - }); - } - - void _sortActiveMods() async { - if (_activeMods.isEmpty) { - setState(() { - _statusMessage = 'No active mods to sort'; - }); - return; - } - - setState(() { - _isLoading = true; - _statusMessage = 'Sorting mods based on dependencies...'; - _hasCycles = false; - _cycleInfo = null; - _incompatibleMods = []; - _loadOrderErrors = null; - }); - - // Use a Future.delayed to allow the UI to update - await Future.delayed(Duration.zero); - - try { - final logger = Logger.instance; - logger.info('Starting auto-sort of ${_activeMods.length} active mods'); - - // Generate a load order for active mods - final loadOrder = modManager.generateLoadOrder(); - - // Store all errors - if (loadOrder.hasErrors) { - setState(() { - _loadOrderErrors = List.from(loadOrder.errors); - }); - - logger.warning( - 'Found ${loadOrder.errors.length} errors during sorting', - ); - for (final error in loadOrder.errors) { - logger.warning(' - $error'); - } - - setState(() { - _hasCycles = loadOrder.errors.any( - (e) => e.contains('Cyclic dependency'), - ); - if (_hasCycles) { - // Extract cycle info from error message - final cycleError = loadOrder.errors.firstWhere( - (e) => e.contains('Cyclic dependency'), - orElse: () => '', - ); - logger.warning('Detected dependency cycle: $cycleError'); - - if (cycleError.isNotEmpty) { - // Extract cycle path from error message - final startIndex = cycleError.indexOf(':'); - if (startIndex != -1) { - final pathStr = cycleError.substring(startIndex + 1).trim(); - _cycleInfo = pathStr.split(' -> '); - logger.info( - 'Extracted cycle path: ${_cycleInfo!.join(" -> ")}', - ); - } - } - } - }); - } else { - _loadOrderErrors = null; - } - - // Check for incompatibilities - _incompatibleMods = modManager.checkIncompatibilities( - modManager.activeMods.keys.toList(), - ); - - if (_incompatibleMods.isNotEmpty) { - logger.warning( - 'Found ${_incompatibleMods.length} incompatible mod pairs:', - ); - for (final pair in _incompatibleMods) { - final mod1 = modManager.mods[pair[0]]?.name ?? pair[0]; - final mod2 = modManager.mods[pair[1]]?.name ?? pair[1]; - logger.warning(' - $mod1 is incompatible with $mod2'); - } - } - - // Get sorted mods from the load order - final List sortedMods = []; - for (final modId in loadOrder.loadOrder) { - if (modManager.mods.containsKey(modId)) { - sortedMods.add(modManager.mods[modId]!); - } - } - - logger.info( - 'Sorting complete. Arranged ${sortedMods.length} mods in load order', - ); - if (sortedMods.isNotEmpty) { - logger.info( - 'First 5 mods in order: ${sortedMods.take(5).map((m) => m.name).join(', ')}', - ); - if (sortedMods.length > 5) { - logger.info('... (${sortedMods.length - 5} more) ...'); - logger.info( - 'Last 3 mods in order: ${sortedMods.reversed.take(3).map((m) => m.name).toList().reversed.join(', ')}', - ); - } - } - - setState(() { - _activeMods = sortedMods; - _isLoading = false; - _statusMessage = 'Sorting complete! ${sortedMods.length} mods sorted.'; - if (_hasCycles) { - _statusMessage += ' Warning: Dependency cycles were found and fixed.'; - } - if (_incompatibleMods.isNotEmpty) { - _statusMessage += - ' Warning: ${_incompatibleMods.length} incompatible mod pairs found.'; - } - }); - } catch (e) { - Logger.instance.error('Error during auto-sort: $e'); - setState(() { - _isLoading = false; - _statusMessage = 'Error sorting mods: $e'; - }); - } - } - - void _saveModOrder() async { - if (_activeMods.isEmpty) { - setState(() { - _statusMessage = 'No active mods to save'; - }); - return; - } - - setState(() { - _isLoading = true; - _statusMessage = 'Saving mod load order...'; - }); - - try { - final logger = Logger.instance; - logger.info( - 'Saving mod load order for ${_activeMods.length} active mods to $configPath', - ); - - // Save the mod list to the XML config file - final file = File(configPath); - final buffer = StringBuffer(); - - buffer.writeln(''); - buffer.writeln(''); - buffer.writeln(' 1'); - - // Write active mods - buffer.writeln(' '); - for (final mod in _activeMods) { - buffer.writeln('
  • ${mod.id}
  • '); - logger.info(' - Adding mod to config: ${mod.name} (${mod.id})'); - } - buffer.writeln('
    '); - - // Count expansions - final expansions = _availableMods.where((m) => m.isExpansion).toList(); - logger.info('Found ${expansions.length} expansions to include in config'); - - // Add known expansions - buffer.writeln(' '); - for (final mod in expansions) { - buffer.writeln('
  • ${mod.id}
  • '); - logger.info(' - Adding expansion to config: ${mod.name} (${mod.id})'); - } - buffer.writeln('
    '); - - buffer.writeln('
    '); - - // Ensure directory exists - final directory = Directory(configRoot); - if (!directory.existsSync()) { - logger.info('Creating config directory: $configRoot'); - directory.createSync(recursive: true); - } - - // Write to file - logger.info('Writing config file to $configPath'); - await file.writeAsString(buffer.toString()); - logger.info('Successfully saved mod configuration'); - - setState(() { - _isLoading = false; - _statusMessage = 'Mod load order saved successfully!'; - }); - - // Show success message - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Mod order saved to config. ${_activeMods.length} mods enabled.', - ), - backgroundColor: Colors.green, - ), - ); - } catch (e) { - final logger = Logger.instance; - logger.error('Error saving mod load order: $e'); - - setState(() { - _isLoading = false; - _statusMessage = 'Error saving mod load order: $e'; - }); - - // Show error message - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error saving mod order: $e'), - backgroundColor: Colors.red, - ), - ); - } - } - - // Load all required dependencies for active mods - void _loadRequiredDependencies() async { - if (_activeMods.isEmpty) { - setState(() { - _statusMessage = 'No active mods to load dependencies for'; - }); - return; - } - - setState(() { - _isLoading = true; - _statusMessage = 'Loading required dependencies...'; - _hasCycles = false; - _cycleInfo = null; - _incompatibleMods = []; - _loadOrderErrors = null; - }); - - // Use a Future.delayed to allow the UI to update - await Future.delayed(Duration.zero); - - try { - final logger = Logger.instance; - logger.info( - 'Starting dependency resolution for ${_activeMods.length} active mods', - ); - - // Get current active mod count for comparison - final initialActiveCount = modManager.activeMods.length; - - // Load required dependencies and get the load order - final loadOrder = modManager.loadRequired(); - - // Store any errors - if (loadOrder.hasErrors) { - setState(() { - _loadOrderErrors = List.from(loadOrder.errors); - }); - - logger.warning( - 'Found ${loadOrder.errors.length} errors during dependency loading', - ); - for (final error in loadOrder.errors) { - logger.warning(' - $error'); - } - - // Check for cycles - setState(() { - _hasCycles = loadOrder.errors.any( - (e) => e.contains('Cyclic dependency'), - ); - if (_hasCycles) { - // Extract cycle info from error message - final cycleError = loadOrder.errors.firstWhere( - (e) => e.contains('Cyclic dependency'), - orElse: () => '', - ); - logger.warning('Detected dependency cycle: $cycleError'); - - if (cycleError.isNotEmpty) { - // Extract cycle path from error message - final startIndex = cycleError.indexOf(':'); - if (startIndex != -1) { - final pathStr = cycleError.substring(startIndex + 1).trim(); - _cycleInfo = pathStr.split(' -> '); - logger.info( - 'Extracted cycle path: ${_cycleInfo!.join(" -> ")}', - ); - } - } - } - }); - } else { - _loadOrderErrors = null; - } - - // Check for incompatibilities - _incompatibleMods = modManager.checkIncompatibilities( - modManager.activeMods.keys.toList(), - ); - - if (_incompatibleMods.isNotEmpty) { - logger.warning( - 'Found ${_incompatibleMods.length} incompatible mod pairs:', - ); - for (final pair in _incompatibleMods) { - final mod1 = modManager.mods[pair[0]]?.name ?? pair[0]; - final mod2 = modManager.mods[pair[1]]?.name ?? pair[1]; - logger.warning(' - $mod1 is incompatible with $mod2'); - } - } - - // Get sorted mods from the load order - final List sortedMods = []; - for (final modId in loadOrder.loadOrder) { - if (modManager.mods.containsKey(modId)) { - sortedMods.add(modManager.mods[modId]!); - } - } - - // Calculate how many dependencies were added - final newModsCount = modManager.activeMods.length - initialActiveCount; - - logger.info( - 'Dependency loading complete. Enabled $newModsCount new dependencies.', - ); - if (newModsCount > 0) { - logger.info('Newly enabled dependencies:'); - final activeModIds = modManager.activeMods.keys.toList(); - for (int i = 0; i < newModsCount; i++) { - final modId = activeModIds[initialActiveCount + i]; - logger.info(' - ${modManager.mods[modId]?.name ?? modId} ($modId)'); - } - } - - setState(() { - _activeMods = sortedMods; - _isLoading = false; - if (newModsCount > 0) { - _statusMessage = 'Added $newModsCount required dependencies!'; - } else { - _statusMessage = 'All dependencies are already loaded.'; - } - - if (_hasCycles) { - _statusMessage += ' Warning: Dependency cycles were found and fixed.'; - } - if (_incompatibleMods.isNotEmpty) { - _statusMessage += - ' Warning: ${_incompatibleMods.length} incompatible mod pairs found.'; - } - }); - } catch (e) { - Logger.instance.error('Error during dependency loading: $e'); - setState(() { - _isLoading = false; - _statusMessage = 'Error loading dependencies: $e'; - }); - } - } -} - -// Page for troubleshooting problematic mods -class TroubleshootingPage extends StatelessWidget { - const TroubleshootingPage({super.key}); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.build, size: 64), - const SizedBox(height: 16), - Text( - 'Troubleshooting', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Text( - 'Find problematic mods with smart batch testing.', - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, - ), - ), - const SizedBox(height: 24), - ElevatedButton( - onPressed: () { - // TODO: Implement troubleshooting wizard - }, - child: const Text('Start Troubleshooting'), - ), - ], - ), - ); - } -} +//import 'dart:io'; +// +//import 'package:flutter/material.dart'; +//import 'package:rimworld_modman/logger.dart'; +//import 'package:rimworld_modman/mod.dart'; +//import 'package:rimworld_modman/mod_list.dart'; +// +//// Constants for file paths +//final String root = +// Platform.isWindows +// ? r'C:/Users/Administrator/Seafile/Games-RimWorld' +// : '~/Library/Application Support/RimWorld'; +//final String modsRoot = Platform.isWindows ? '$root/294100' : '$root/Mods'; +//final String configRoot = +// Platform.isWindows +// ? '$root/AppData/RimWorld by Ludeon Studios/Config' +// : '$root/Config'; +//final String configPath = '$configRoot/ModsConfig.xml'; +//final String logsPath = '$root/ModManager'; +// +//late ModList modManager; +// +//void main() { +// WidgetsFlutterBinding.ensureInitialized(); +// +// // Get a reference to the logger (now auto-initializes) +// final logger = Logger.instance; +// logger.info('RimWorld Mod Manager starting...'); +// +// // Initialize the mod manager +// modManager = ModList(modsPath: modsRoot, configPath: configPath); +// +// // Start the app +// runApp(const RimWorldModManager()); +//} +// +//class RimWorldModManager extends StatelessWidget { +// const RimWorldModManager({super.key}); +// +// @override +// Widget build(BuildContext context) { +// return MaterialApp( +// title: 'RimWorld Mod Manager', +// theme: ThemeData.dark().copyWith( +// primaryColor: const Color(0xFF3D4A59), +// colorScheme: ColorScheme.fromSeed( +// seedColor: const Color(0xFF3D4A59), +// brightness: Brightness.dark, +// ), +// scaffoldBackgroundColor: const Color(0xFF1E262F), +// cardColor: const Color(0xFF2A3440), +// appBarTheme: const AppBarTheme( +// backgroundColor: Color(0xFF2A3440), +// foregroundColor: Colors.white, +// ), +// ), +// home: const ModManagerHomePage(), +// ); +// } +//} +// +//class ModManagerHomePage extends StatefulWidget { +// const ModManagerHomePage({super.key}); +// +// @override +// State createState() => _ModManagerHomePageState(); +//} +// +//class _ModManagerHomePageState extends State { +// int _selectedIndex = 0; +// +// final List _pages = [ +// const ModManagerPage(), +// const TroubleshootingPage(), +// ]; +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar(title: const Text('RimWorld Mod Manager')), +// body: _pages[_selectedIndex], +// bottomNavigationBar: BottomNavigationBar( +// currentIndex: _selectedIndex, +// onTap: (index) { +// setState(() { +// _selectedIndex = index; +// }); +// }, +// items: const [ +// BottomNavigationBarItem(icon: Icon(Icons.extension), label: 'Mods'), +// BottomNavigationBarItem( +// icon: Icon(Icons.build), +// label: 'Troubleshoot', +// ), +// ], +// ), +// ); +// } +//} +// +//// Combined page for mod management with two-panel layout +//class ModManagerPage extends StatefulWidget { +// const ModManagerPage({super.key}); +// +// @override +// State createState() => _ModManagerPageState(); +//} +// +//class _ModManagerPageState extends State { +// // For all available mods (left panel) +// List _availableMods = []; +// +// // For active mods (right panel) +// List _activeMods = []; +// +// bool _isLoading = false; +// String _statusMessage = ''; +// int _totalModsFound = 0; +// bool _skipFileCount = false; +// bool _hasCycles = false; +// List? _cycleInfo; +// List> _incompatibleMods = []; +// List? _loadOrderErrors; +// +// final TextEditingController _searchController = TextEditingController(); +// String _searchQuery = ''; +// +// @override +// void initState() { +// super.initState(); +// // Check if mods are already loaded +// if (modManager.mods.isNotEmpty) { +// _loadModsFromGlobalState(); +// } +// +// _searchController.addListener(() { +// setState(() { +// _searchQuery = _searchController.text.toLowerCase(); +// }); +// }); +// } +// +// @override +// void dispose() { +// _searchController.dispose(); +// super.dispose(); +// } +// +// void _loadModsFromGlobalState() { +// setState(() { +// // Get all mods for the left panel (sorted alphabetically) +// _availableMods = modManager.mods.values.toList(); +// _availableMods.sort((a, b) => a.name.compareTo(b.name)); +// +// // Get active mods for the right panel (in load order) +// _activeMods = modManager.mods.values.where((m) => m.enabled).toList(); +// _isLoading = false; +// _statusMessage = 'Loaded ${_availableMods.length} mods'; +// _totalModsFound = _availableMods.length; +// }); +// } +// +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// body: +// _isLoading && _availableMods.isEmpty +// ? _buildLoadingView() +// : _availableMods.isEmpty +// ? _buildEmptyState() +// : _buildSplitView(), +// ); +// } +// +// Widget _buildLoadingView() { +// return Center( +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// const CircularProgressIndicator(), +// const SizedBox(height: 16), +// Text( +// _statusMessage, +// style: Theme.of(context).textTheme.bodyMedium, +// textAlign: TextAlign.center, +// ), +// ], +// ), +// ); +// } +// +// Widget _buildEmptyState() { +// return Center( +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// const Icon(Icons.extension, size: 64), +// const SizedBox(height: 16), +// Text( +// 'Mod Manager', +// style: Theme.of(context).textTheme.headlineMedium, +// ), +// const SizedBox(height: 16), +// Text( +// 'Ready to scan for RimWorld mods.', +// style: Theme.of(context).textTheme.bodyLarge, +// ), +// const SizedBox(height: 12), +// Row( +// mainAxisSize: MainAxisSize.min, +// children: [ +// Checkbox( +// value: _skipFileCount, +// onChanged: (value) { +// setState(() { +// _skipFileCount = value ?? true; +// }); +// }, +// ), +// const Text('Skip file counting (faster loading)'), +// ], +// ), +// const SizedBox(height: 24), +// ElevatedButton( +// onPressed: _startLoadingMods, +// child: const Text('Scan for Mods'), +// ), +// ], +// ), +// ); +// } +// +// Widget _buildSplitView() { +// // Filter both available and active mods based on search query +// final filteredAvailableMods = +// _searchQuery.isEmpty +// ? _availableMods +// : _availableMods +// .where( +// (mod) => +// mod.name.toLowerCase().contains(_searchQuery) || +// mod.id.toLowerCase().contains(_searchQuery), +// ) +// .toList(); +// +// final filteredActiveMods = +// _searchQuery.isEmpty +// ? _activeMods +// : _activeMods +// .where( +// (mod) => +// mod.name.toLowerCase().contains(_searchQuery) || +// mod.id.toLowerCase().contains(_searchQuery), +// ) +// .toList(); +// +// return Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(8.0), +// child: Row( +// children: [ +// // Search field +// Expanded( +// child: TextField( +// controller: _searchController, +// decoration: InputDecoration( +// hintText: 'Search mods by name or ID...', +// prefixIcon: const Icon(Icons.search), +// border: OutlineInputBorder( +// borderRadius: BorderRadius.circular(8.0), +// ), +// suffixIcon: +// _searchQuery.isNotEmpty +// ? IconButton( +// icon: const Icon(Icons.clear), +// onPressed: () { +// _searchController.clear(); +// }, +// ) +// : null, +// ), +// ), +// ), +// const SizedBox(width: 8), +// // Reload button +// IconButton( +// icon: const Icon(Icons.refresh), +// tooltip: 'Reload mods', +// onPressed: _startLoadingMods, +// ), +// const SizedBox(width: 8), +// // Load Dependencies button +// Tooltip( +// message: +// 'Automatically load missing dependencies for active mods', +// child: ElevatedButton.icon( +// icon: const Icon(Icons.download), +// label: const Text('Load Deps'), +// onPressed: _loadRequiredDependencies, +// ), +// ), +// const SizedBox(width: 8), +// // Auto-sort button +// ElevatedButton.icon( +// icon: const Icon(Icons.sort), +// label: const Text('Auto-Sort'), +// onPressed: _sortActiveMods, +// ), +// const SizedBox(width: 8), +// // Save button +// ElevatedButton.icon( +// icon: const Icon(Icons.save), +// label: const Text('Save'), +// onPressed: _saveModOrder, +// ), +// ], +// ), +// ), +// +// // Status message +// if (!_isLoading && _statusMessage.isNotEmpty) +// Padding( +// padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), +// child: Text( +// _statusMessage, +// style: TextStyle( +// color: +// _hasCycles || _incompatibleMods.isNotEmpty +// ? Colors.orange +// : Colors.green, +// ), +// ), +// ), +// +// // Error display section +// if (_hasCycles || +// _incompatibleMods.isNotEmpty || +// (_loadOrderErrors?.isNotEmpty ?? false)) +// Container( +// margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), +// padding: const EdgeInsets.all(8.0), +// decoration: BoxDecoration( +// color: Colors.red.shade900.withOpacity(0.3), +// borderRadius: BorderRadius.circular(4.0), +// border: Border.all(color: Colors.red.shade800), +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// // Cycle warnings +// if (_hasCycles && _cycleInfo != null) +// Padding( +// padding: const EdgeInsets.only(bottom: 4.0), +// child: Row( +// children: [ +// const Icon(Icons.loop, color: Colors.orange, size: 16), +// const SizedBox(width: 4), +// Expanded( +// child: Text( +// 'Dependency cycle detected: ${_cycleInfo!.join(" -> ")}', +// style: const TextStyle(color: Colors.orange), +// ), +// ), +// ], +// ), +// ), +// +// // Incompatible mod warnings +// if (_incompatibleMods.isNotEmpty) +// Padding( +// padding: const EdgeInsets.only(bottom: 4.0), +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// const Icon( +// Icons.warning, +// color: Colors.orange, +// size: 16, +// ), +// const SizedBox(width: 4), +// Expanded( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text( +// '${_incompatibleMods.length} incompatible mod pairs:', +// style: const TextStyle(color: Colors.orange), +// ), +// if (_incompatibleMods.length <= 3) +// ...List.generate(_incompatibleMods.length, ( +// index, +// ) { +// final pair = _incompatibleMods[index]; +// final mod1 = +// modManager.mods[pair[0]]?.name ?? pair[0]; +// final mod2 = +// modManager.mods[pair[1]]?.name ?? pair[1]; +// return Padding( +// padding: const EdgeInsets.only( +// left: 12.0, +// top: 2.0, +// ), +// child: Text( +// '• $mod1 ↔ $mod2', +// style: TextStyle( +// color: Colors.orange.shade300, +// fontSize: 12, +// ), +// ), +// ); +// }), +// ], +// ), +// ), +// ], +// ), +// ), +// +// // Other errors (missing dependencies, etc.) +// if (_loadOrderErrors?.isNotEmpty ?? false) +// Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// const Icon( +// Icons.error_outline, +// color: Colors.red, +// size: 16, +// ), +// const SizedBox(width: 4), +// Expanded( +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// const Text( +// 'Dependency errors:', +// style: TextStyle(color: Colors.red), +// ), +// ...List.generate( +// _loadOrderErrors!.length > 5 +// ? 5 +// : _loadOrderErrors!.length, +// (index) => Padding( +// padding: const EdgeInsets.only( +// left: 12.0, +// top: 2.0, +// ), +// child: Text( +// '• ${_loadOrderErrors![index]}', +// style: TextStyle( +// color: Colors.red.shade300, +// fontSize: 12, +// ), +// ), +// ), +// ), +// if (_loadOrderErrors!.length > 5) +// Padding( +// padding: const EdgeInsets.only( +// left: 12.0, +// top: 4.0, +// ), +// child: Text( +// '(${_loadOrderErrors!.length - 5} more errors...)', +// style: TextStyle( +// color: Colors.red.shade300, +// fontSize: 12, +// fontStyle: FontStyle.italic, +// ), +// ), +// ), +// ], +// ), +// ), +// ], +// ), +// ], +// ), +// ), +// +// // Main split view +// Expanded( +// child: Row( +// children: [ +// // LEFT PANEL - All available mods (alphabetical) +// Expanded( +// child: Card( +// margin: const EdgeInsets.all(8.0), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// Container( +// color: Theme.of(context).primaryColor, +// padding: const EdgeInsets.all(8.0), +// child: Text( +// 'Available Mods (${filteredAvailableMods.length}${_searchQuery.isNotEmpty ? '/${_availableMods.length}' : ''})', +// style: const TextStyle(fontWeight: FontWeight.bold), +// textAlign: TextAlign.center, +// ), +// ), +// if (_searchQuery.isNotEmpty) +// Padding( +// padding: const EdgeInsets.all(4.0), +// child: Text( +// 'Searching: "$_searchQuery"', +// style: TextStyle( +// fontSize: 12, +// fontStyle: FontStyle.italic, +// color: Colors.grey.shade400, +// ), +// textAlign: TextAlign.center, +// ), +// ), +// Expanded( +// child: ListView.builder( +// itemCount: filteredAvailableMods.length, +// itemBuilder: (context, index) { +// final mod = filteredAvailableMods[index]; +// final isActive = mod.enabled; +// +// return GestureDetector( +// onDoubleTap: () => _toggleModActive(mod), +// child: ListTile( +// title: Text( +// mod.name, +// style: TextStyle( +// color: +// isActive ? Colors.green : Colors.white, +// ), +// ), +// subtitle: Text( +// 'ID: ${mod.id}\nSize: ${mod.size} files', +// style: Theme.of(context).textTheme.bodySmall, +// ), +// trailing: Row( +// mainAxisSize: MainAxisSize.min, +// children: [ +// if (mod.isBaseGame) +// const Tooltip( +// message: 'Base Game', +// child: Icon( +// Icons.home, +// color: Colors.blue, +// size: 24, +// ), +// ), +// if (mod.isExpansion) +// const Tooltip( +// message: 'Expansion', +// child: Icon( +// Icons.star, +// color: Colors.yellow, +// size: 24, +// ), +// ), +// const SizedBox(width: 4), +// if (mod.dependencies.isNotEmpty) +// Tooltip( +// message: +// 'Dependencies:\n${mod.dependencies.join('\n')}', +// child: const Icon( +// Icons.link, +// color: Colors.orange, +// size: 24, +// ), +// ), +// if (mod.loadAfter.isNotEmpty) +// Tooltip( +// message: +// 'Loads after:\n${mod.loadAfter.join('\n')}', +// child: const Icon( +// Icons.arrow_downward, +// color: Colors.blue, +// size: 24, +// ), +// ), +// if (mod.loadBefore.isNotEmpty) +// Tooltip( +// message: +// 'Loads before:\n${mod.loadBefore.join('\n')}', +// child: const Icon( +// Icons.arrow_upward, +// color: Colors.green, +// size: 24, +// ), +// ), +// ], +// ), +// onTap: () { +// // Show mod details in future +// }, +// ), +// ); +// }, +// ), +// ), +// ], +// ), +// ), +// ), +// +// // RIGHT PANEL - Active mods (load order) +// Expanded( +// child: Card( +// margin: const EdgeInsets.all(8.0), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// Container( +// color: Theme.of(context).primaryColor, +// padding: const EdgeInsets.all(8.0), +// child: Text( +// 'Active Mods (${filteredActiveMods.length}${_searchQuery.isNotEmpty ? '/${_activeMods.length}' : ''})', +// style: const TextStyle(fontWeight: FontWeight.bold), +// textAlign: TextAlign.center, +// ), +// ), +// Padding( +// padding: const EdgeInsets.all(8.0), +// child: Text( +// _searchQuery.isNotEmpty +// ? 'Searching: "$_searchQuery"' +// : 'Larger mods are prioritized during auto-sorting.', +// style: TextStyle( +// fontSize: 12, +// color: Colors.grey.shade400, +// fontStyle: FontStyle.italic, +// ), +// textAlign: TextAlign.center, +// ), +// ), +// Expanded( +// child: ListView.builder( +// itemCount: filteredActiveMods.length, +// itemBuilder: (context, index) { +// final mod = filteredActiveMods[index]; +// // Find the actual position in the complete active mods list (for correct load order) +// final actualLoadOrderPosition = +// _activeMods.indexWhere((m) => m.id == mod.id) + +// 1; +// +// return GestureDetector( +// onDoubleTap: () { +// // Don't allow deactivating base game or expansions +// if (!mod.isBaseGame && !mod.isExpansion) { +// _toggleModActive(mod); +// } else { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text( +// 'Core game and expansions cannot be deactivated', +// ), +// backgroundColor: Colors.orange, +// ), +// ); +// } +// }, +// child: Card( +// margin: const EdgeInsets.symmetric( +// horizontal: 8.0, +// vertical: 4.0, +// ), +// child: ListTile( +// leading: SizedBox( +// width: 50, +// child: Column( +// mainAxisSize: MainAxisSize.min, +// mainAxisAlignment: +// MainAxisAlignment.center, +// children: [ +// SizedBox( +// height: 24, +// child: Container( +// padding: const EdgeInsets.symmetric( +// horizontal: 4, +// ), +// decoration: BoxDecoration( +// color: +// _searchQuery.isNotEmpty +// ? Colors.blue.withOpacity( +// 0.2, +// ) +// : null, +// borderRadius: +// BorderRadius.circular(4), +// ), +// child: Tooltip( +// message: +// 'Load position: $actualLoadOrderPosition of ${_activeMods.length}${_searchQuery.isNotEmpty ? " (preserved when filtering)" : ""}', +// child: Text( +// '$actualLoadOrderPosition', +// style: TextStyle( +// fontWeight: FontWeight.bold, +// fontSize: 12, +// color: +// _searchQuery.isNotEmpty +// ? Colors.blue.shade300 +// : null, +// ), +// ), +// ), +// ), +// ), +// SizedBox( +// height: 24, +// child: Center( +// child: +// mod.size > 0 +// ? Tooltip( +// message: +// 'This mod contains ${mod.size} files.', +// child: Text( +// '${mod.size}', +// style: const TextStyle( +// fontSize: 10, +// color: Colors.grey, +// ), +// ), +// ) +// : const SizedBox(), +// ), +// ), +// ], +// ), +// ), +// title: Text(mod.name), +// subtitle: Text( +// mod.id, +// style: const TextStyle(fontSize: 12), +// ), +// trailing: Row( +// mainAxisSize: MainAxisSize.min, +// children: [ +// if (mod.isBaseGame) +// const Tooltip( +// message: 'Base Game', +// child: Icon( +// Icons.home, +// color: Colors.blue, +// size: 24, +// ), +// ), +// if (mod.isExpansion) +// const Tooltip( +// message: 'Expansion', +// child: Icon( +// Icons.star, +// color: Colors.yellow, +// size: 24, +// ), +// ), +// if (mod.dependencies.isNotEmpty) +// Tooltip( +// message: +// 'Dependencies:\n${mod.dependencies.join('\n')}', +// child: const 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: const Icon( +// Icons.arrow_downward, +// 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: const Icon( +// Icons.arrow_upward, +// color: Colors.green, +// size: 24, +// ), +// ), +// ], +// ), +// onTap: () { +// // Show mod details in future +// }, +// ), +// ), +// ); +// }, +// ), +// ), +// ], +// ), +// ), +// ), +// ], +// ), +// ), +// ], +// ); +// } +// +// void _startLoadingMods() { +// setState(() { +// _availableMods.clear(); +// _activeMods.clear(); +// _isLoading = true; +// _statusMessage = 'Scanning for mods...'; +// _hasCycles = false; +// _cycleInfo = null; +// _incompatibleMods = []; +// }); +// +// // Create an async function to load mods +// Future loadMods() async { +// try { +// // First load available mods +// await for (final mod in modManager.loadAvailable()) { +// // Update UI for each mod loaded +// if (mounted) { +// setState(() { +// _statusMessage = 'Loaded mod: ${mod.name}'; +// _totalModsFound = modManager.mods.length; +// }); +// } +// } +// +// // Then load active mods from config +// await for (final mod in modManager.loadActive()) { +// // Update UI as active mods are loaded +// if (mounted) { +// setState(() { +// _statusMessage = 'Loading active mod: ${mod.name}'; +// }); +// } +// } +// +// // Update the UI with all loaded mods +// if (mounted) { +// _loadModsFromGlobalState(); +// setState(() { +// _statusMessage = +// 'Loaded ${_availableMods.length} mods, ${_activeMods.length} active'; +// }); +// } +// } catch (error) { +// if (mounted) { +// setState(() { +// _isLoading = false; +// _statusMessage = 'Error loading mods: $error'; +// }); +// } +// } +// } +// +// // Start the loading process +// loadMods(); +// } +// +// void _toggleModActive(Mod mod) { +// // Cannot deactivate base game or expansions +// if ((mod.isBaseGame || mod.isExpansion) && mod.enabled) { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text('Core game and expansions cannot be deactivated'), +// backgroundColor: Colors.orange, +// ), +// ); +// return; +// } +// +// // Get the current state before toggling +// final bool wasEnabled = mod.enabled; +// +// // Toggle the mod in the global mod manager +// modManager.setEnabled(mod.id, !wasEnabled); +// Logger.instance.info( +// 'Toggled mod ${mod.name} (${mod.id}) from ${wasEnabled ? 'enabled' : 'disabled'} to ${!wasEnabled ? 'enabled' : 'disabled'}', +// ); +// +// // Update the UI +// setState(() { +// // Update in the available mods list +// final index = _availableMods.indexWhere((m) => m.id == mod.id); +// if (index >= 0) { +// _availableMods[index] = modManager.mods[mod.id]!; +// } +// +// // Update the active mods list +// _activeMods = modManager.mods.values.where((m) => m.enabled).toList(); +// +// _statusMessage = +// 'Mod "${mod.name}" ${!wasEnabled ? 'activated' : 'deactivated'}'; +// }); +// } +// +// void _sortActiveMods() async { +// if (_activeMods.isEmpty) { +// setState(() { +// _statusMessage = 'No active mods to sort'; +// }); +// return; +// } +// +// setState(() { +// _isLoading = true; +// _statusMessage = 'Sorting mods based on dependencies...'; +// _hasCycles = false; +// _cycleInfo = null; +// _incompatibleMods = []; +// _loadOrderErrors = null; +// }); +// +// // Use a Future.delayed to allow the UI to update +// await Future.delayed(Duration.zero); +// +// try { +// final logger = Logger.instance; +// logger.info('Starting auto-sort of ${_activeMods.length} active mods'); +// +// // Generate a load order for active mods +// final loadOrder = modManager.generateLoadOrder(); +// +// // Store all errors +// if (loadOrder.hasErrors) { +// setState(() { +// _loadOrderErrors = List.from(loadOrder.errors); +// }); +// +// logger.warning( +// 'Found ${loadOrder.errors.length} errors during sorting', +// ); +// for (final error in loadOrder.errors) { +// logger.warning(' - $error'); +// } +// +// setState(() { +// _hasCycles = loadOrder.errors.any( +// (e) => e.contains('Cyclic dependency'), +// ); +// if (_hasCycles) { +// // Extract cycle info from error message +// final cycleError = loadOrder.errors.firstWhere( +// (e) => e.contains('Cyclic dependency'), +// orElse: () => '', +// ); +// logger.warning('Detected dependency cycle: $cycleError'); +// +// if (cycleError.isNotEmpty) { +// // Extract cycle path from error message +// final startIndex = cycleError.indexOf(':'); +// if (startIndex != -1) { +// final pathStr = cycleError.substring(startIndex + 1).trim(); +// _cycleInfo = pathStr.split(' -> '); +// logger.info( +// 'Extracted cycle path: ${_cycleInfo!.join(" -> ")}', +// ); +// } +// } +// } +// }); +// } else { +// _loadOrderErrors = null; +// } +// +// // Check for incompatibilities +// _incompatibleMods = modManager.checkIncompatibilities( +// modManager.activeMods.keys.toList(), +// ); +// +// if (_incompatibleMods.isNotEmpty) { +// logger.warning( +// 'Found ${_incompatibleMods.length} incompatible mod pairs:', +// ); +// for (final pair in _incompatibleMods) { +// final mod1 = modManager.mods[pair[0]]?.name ?? pair[0]; +// final mod2 = modManager.mods[pair[1]]?.name ?? pair[1]; +// logger.warning(' - $mod1 is incompatible with $mod2'); +// } +// } +// +// // Get sorted mods from the load order +// final List sortedMods = []; +// for (final modId in loadOrder.loadOrder) { +// if (modManager.mods.containsKey(modId)) { +// sortedMods.add(modManager.mods[modId]!); +// } +// } +// +// logger.info( +// 'Sorting complete. Arranged ${sortedMods.length} mods in load order', +// ); +// if (sortedMods.isNotEmpty) { +// logger.info( +// 'First 5 mods in order: ${sortedMods.take(5).map((m) => m.name).join(', ')}', +// ); +// if (sortedMods.length > 5) { +// logger.info('... (${sortedMods.length - 5} more) ...'); +// logger.info( +// 'Last 3 mods in order: ${sortedMods.reversed.take(3).map((m) => m.name).toList().reversed.join(', ')}', +// ); +// } +// } +// +// setState(() { +// _activeMods = sortedMods; +// _isLoading = false; +// _statusMessage = 'Sorting complete! ${sortedMods.length} mods sorted.'; +// if (_hasCycles) { +// _statusMessage += ' Warning: Dependency cycles were found and fixed.'; +// } +// if (_incompatibleMods.isNotEmpty) { +// _statusMessage += +// ' Warning: ${_incompatibleMods.length} incompatible mod pairs found.'; +// } +// }); +// } catch (e) { +// Logger.instance.error('Error during auto-sort: $e'); +// setState(() { +// _isLoading = false; +// _statusMessage = 'Error sorting mods: $e'; +// }); +// } +// } +// +// void _saveModOrder() async { +// if (_activeMods.isEmpty) { +// setState(() { +// _statusMessage = 'No active mods to save'; +// }); +// return; +// } +// +// setState(() { +// _isLoading = true; +// _statusMessage = 'Saving mod load order...'; +// }); +// +// try { +// final logger = Logger.instance; +// logger.info( +// 'Saving mod load order for ${_activeMods.length} active mods to $configPath', +// ); +// +// // Save the mod list to the XML config file +// final file = File(configPath); +// final buffer = StringBuffer(); +// +// buffer.writeln(''); +// buffer.writeln(''); +// buffer.writeln(' 1'); +// +// // Write active mods +// buffer.writeln(' '); +// for (final mod in _activeMods) { +// buffer.writeln('
  • ${mod.id}
  • '); +// logger.info(' - Adding mod to config: ${mod.name} (${mod.id})'); +// } +// buffer.writeln('
    '); +// +// // Count expansions +// final expansions = _availableMods.where((m) => m.isExpansion).toList(); +// logger.info('Found ${expansions.length} expansions to include in config'); +// +// // Add known expansions +// buffer.writeln(' '); +// for (final mod in expansions) { +// buffer.writeln('
  • ${mod.id}
  • '); +// logger.info(' - Adding expansion to config: ${mod.name} (${mod.id})'); +// } +// buffer.writeln('
    '); +// +// buffer.writeln('
    '); +// +// // Ensure directory exists +// final directory = Directory(configRoot); +// if (!directory.existsSync()) { +// logger.info('Creating config directory: $configRoot'); +// directory.createSync(recursive: true); +// } +// +// // Write to file +// logger.info('Writing config file to $configPath'); +// await file.writeAsString(buffer.toString()); +// logger.info('Successfully saved mod configuration'); +// +// setState(() { +// _isLoading = false; +// _statusMessage = 'Mod load order saved successfully!'; +// }); +// +// // Show success message +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar( +// content: Text( +// 'Mod order saved to config. ${_activeMods.length} mods enabled.', +// ), +// backgroundColor: Colors.green, +// ), +// ); +// } catch (e) { +// final logger = Logger.instance; +// logger.error('Error saving mod load order: $e'); +// +// setState(() { +// _isLoading = false; +// _statusMessage = 'Error saving mod load order: $e'; +// }); +// +// // Show error message +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar( +// content: Text('Error saving mod order: $e'), +// backgroundColor: Colors.red, +// ), +// ); +// } +// } +// +// // Load all required dependencies for active mods +// void _loadRequiredDependencies() async { +// if (_activeMods.isEmpty) { +// setState(() { +// _statusMessage = 'No active mods to load dependencies for'; +// }); +// return; +// } +// +// setState(() { +// _isLoading = true; +// _statusMessage = 'Loading required dependencies...'; +// _hasCycles = false; +// _cycleInfo = null; +// _incompatibleMods = []; +// _loadOrderErrors = null; +// }); +// +// // Use a Future.delayed to allow the UI to update +// await Future.delayed(Duration.zero); +// +// try { +// final logger = Logger.instance; +// logger.info( +// 'Starting dependency resolution for ${_activeMods.length} active mods', +// ); +// +// // Get current active mod count for comparison +// final initialActiveCount = modManager.activeMods.length; +// +// // Load required dependencies and get the load order +// final loadOrder = modManager.loadRequired(); +// +// // Store any errors +// if (loadOrder.hasErrors) { +// setState(() { +// _loadOrderErrors = List.from(loadOrder.errors); +// }); +// +// logger.warning( +// 'Found ${loadOrder.errors.length} errors during dependency loading', +// ); +// for (final error in loadOrder.errors) { +// logger.warning(' - $error'); +// } +// +// // Check for cycles +// setState(() { +// _hasCycles = loadOrder.errors.any( +// (e) => e.contains('Cyclic dependency'), +// ); +// if (_hasCycles) { +// // Extract cycle info from error message +// final cycleError = loadOrder.errors.firstWhere( +// (e) => e.contains('Cyclic dependency'), +// orElse: () => '', +// ); +// logger.warning('Detected dependency cycle: $cycleError'); +// +// if (cycleError.isNotEmpty) { +// // Extract cycle path from error message +// final startIndex = cycleError.indexOf(':'); +// if (startIndex != -1) { +// final pathStr = cycleError.substring(startIndex + 1).trim(); +// _cycleInfo = pathStr.split(' -> '); +// logger.info( +// 'Extracted cycle path: ${_cycleInfo!.join(" -> ")}', +// ); +// } +// } +// } +// }); +// } else { +// _loadOrderErrors = null; +// } +// +// // Check for incompatibilities +// _incompatibleMods = modManager.checkIncompatibilities( +// modManager.activeMods.keys.toList(), +// ); +// +// if (_incompatibleMods.isNotEmpty) { +// logger.warning( +// 'Found ${_incompatibleMods.length} incompatible mod pairs:', +// ); +// for (final pair in _incompatibleMods) { +// final mod1 = modManager.mods[pair[0]]?.name ?? pair[0]; +// final mod2 = modManager.mods[pair[1]]?.name ?? pair[1]; +// logger.warning(' - $mod1 is incompatible with $mod2'); +// } +// } +// +// // Get sorted mods from the load order +// final List sortedMods = []; +// for (final modId in loadOrder.loadOrder) { +// if (modManager.mods.containsKey(modId)) { +// sortedMods.add(modManager.mods[modId]!); +// } +// } +// +// // Calculate how many dependencies were added +// final newModsCount = modManager.activeMods.length - initialActiveCount; +// +// logger.info( +// 'Dependency loading complete. Enabled $newModsCount new dependencies.', +// ); +// if (newModsCount > 0) { +// logger.info('Newly enabled dependencies:'); +// final activeModIds = modManager.activeMods.keys.toList(); +// for (int i = 0; i < newModsCount; i++) { +// final modId = activeModIds[initialActiveCount + i]; +// logger.info(' - ${modManager.mods[modId]?.name ?? modId} ($modId)'); +// } +// } +// +// setState(() { +// _activeMods = sortedMods; +// _isLoading = false; +// if (newModsCount > 0) { +// _statusMessage = 'Added $newModsCount required dependencies!'; +// } else { +// _statusMessage = 'All dependencies are already loaded.'; +// } +// +// if (_hasCycles) { +// _statusMessage += ' Warning: Dependency cycles were found and fixed.'; +// } +// if (_incompatibleMods.isNotEmpty) { +// _statusMessage += +// ' Warning: ${_incompatibleMods.length} incompatible mod pairs found.'; +// } +// }); +// } catch (e) { +// Logger.instance.error('Error during dependency loading: $e'); +// setState(() { +// _isLoading = false; +// _statusMessage = 'Error loading dependencies: $e'; +// }); +// } +// } +//} +// +//// Page for troubleshooting problematic mods +//class TroubleshootingPage extends StatelessWidget { +// const TroubleshootingPage({super.key}); +// +// @override +// Widget build(BuildContext context) { +// return Center( +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// const Icon(Icons.build, size: 64), +// const SizedBox(height: 16), +// Text( +// 'Troubleshooting', +// style: Theme.of(context).textTheme.headlineMedium, +// ), +// const SizedBox(height: 16), +// Padding( +// padding: const EdgeInsets.symmetric(horizontal: 32.0), +// child: Text( +// 'Find problematic mods with smart batch testing.', +// style: Theme.of(context).textTheme.bodyLarge, +// textAlign: TextAlign.center, +// ), +// ), +// const SizedBox(height: 24), +// ElevatedButton( +// onPressed: () { +// // TODO: Implement troubleshooting wizard +// }, +// child: const Text('Start Troubleshooting'), +// ), +// ], +// ), +// ); +// } +//} +// \ No newline at end of file