diff --git a/lib/mod_troubleshooter_widget.dart b/lib/mod_troubleshooter_widget.dart index 2e07a08..8e6b262 100644 --- a/lib/mod_troubleshooter_widget.dart +++ b/lib/mod_troubleshooter_widget.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:rimworld_modman/mod.dart'; import 'package:rimworld_modman/mod_list.dart'; import 'package:rimworld_modman/mod_list_troubleshooter.dart'; import 'main.dart'; /// A widget that provides a user interface for the mod troubleshooter functionality. -/// +/// /// This allows users to: /// - Toggle between binary and linear search modes /// - Navigate forward and backward through mod sets @@ -17,74 +16,77 @@ class ModTroubleshooterWidget extends StatefulWidget { const ModTroubleshooterWidget({super.key}); @override - State createState() => _ModTroubleshooterWidgetState(); + State createState() => + _ModTroubleshooterWidgetState(); } class _ModTroubleshooterWidgetState extends State { late ModListTroubleshooter _troubleshooter; - + bool _isInitialized = false; bool _isBinaryMode = false; int _stepSize = 10; - + // Set of mod IDs that have been checked and confirmed to be good final Set _checkedMods = {}; - + // Set of mod IDs that are suspected to cause issues final Set _problemMods = {}; - + // The currently selected mod IDs (for highlighting) List _selectedMods = []; - + // The next potential set of mods (from move calculation) Move? _nextForwardMove; Move? _nextBackwardMove; - + // Controller for step size input late TextEditingController _stepSizeController; - + @override void initState() { super.initState(); _stepSizeController = TextEditingController(text: _stepSize.toString()); } - + @override void dispose() { _stepSizeController.dispose(); super.dispose(); } - + void _initialize() { if (_isInitialized) return; - + // Initialize the troubleshooter with the global mod manager _troubleshooter = ModListTroubleshooter(modManager); - + // Set initial active mods for highlighting if (modManager.activeMods.isNotEmpty) { // Initially select all active mods _selectedMods = List.from(modManager.activeMods.keys); } - + // Calculate initial moves _updateNextMoves(); - + setState(() { _isInitialized = true; }); } - + void _updateNextMoves() { if (_isBinaryMode) { _nextForwardMove = _troubleshooter.binaryForwardMove(); _nextBackwardMove = _troubleshooter.binaryBackwardMove(); } else { _nextForwardMove = _troubleshooter.linearForwardMove(stepSize: _stepSize); - _nextBackwardMove = _troubleshooter.linearBackwardMove(stepSize: _stepSize); + _nextBackwardMove = _troubleshooter.linearBackwardMove( + stepSize: _stepSize, + ); } } - + void _navigateForward() { ModList result; if (_isBinaryMode) { @@ -92,17 +94,17 @@ class _ModTroubleshooterWidgetState extends State { } else { result = _troubleshooter.linearForward(stepSize: _stepSize); } - + // Load all required dependencies for the selected mods - final loadOrder = result.loadRequired(); - - // Use the mods from the load order result + final loadOrder = result.loadRequiredBaseGame(); + + // Use the mods from the load order result setState(() { _selectedMods = loadOrder.loadOrder; _updateNextMoves(); }); } - + void _navigateBackward() { ModList result; if (_isBinaryMode) { @@ -110,45 +112,38 @@ class _ModTroubleshooterWidgetState extends State { } else { result = _troubleshooter.linearBackward(stepSize: _stepSize); } - + // Load all required dependencies for the selected mods - final loadOrder = result.loadRequired(); - + final loadOrder = result.loadRequiredBaseGame(); + // Use the mods from the load order result setState(() { _selectedMods = loadOrder.loadOrder; _updateNextMoves(); }); } - - void _toggleSearchMode() { - setState(() { - _isBinaryMode = !_isBinaryMode; - _updateNextMoves(); - }); - } - + void _markAsGood(String modId) { setState(() { _checkedMods.add(modId); _problemMods.remove(modId); }); } - + void _markAsProblem(String modId) { setState(() { _problemMods.add(modId); _checkedMods.remove(modId); }); } - + void _clearMarks(String modId) { setState(() { _checkedMods.remove(modId); _problemMods.remove(modId); }); } - + void _resetTroubleshooter() { setState(() { _checkedMods.clear(); @@ -157,7 +152,7 @@ class _ModTroubleshooterWidgetState extends State { }); _initialize(); } - + void _saveTroubleshootingConfig() { // Only save if we have a valid selection if (_selectedMods.isEmpty) { @@ -169,23 +164,25 @@ class _ModTroubleshooterWidgetState extends State { ); return; } - + // First disable all mods modManager.disableAll(); - + // Then enable only the selected mods modManager.enableMods(_selectedMods); - + // Save the configuration (we don't have direct access to save method, so show a message) ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('${_selectedMods.length} mods prepared for testing. Please use Save button in the Mods tab to save config.'), + content: Text( + '${_selectedMods.length} mods prepared for testing. Please use Save button in the Mods tab to save config.', + ), backgroundColor: Colors.orange, duration: const Duration(seconds: 4), ), ); } - + void _markSelectedAsGood() { if (_selectedMods.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( @@ -196,14 +193,14 @@ class _ModTroubleshooterWidgetState extends State { ); return; } - + setState(() { for (final modId in _selectedMods) { _checkedMods.add(modId); _problemMods.remove(modId); } }); - + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Marked ${_selectedMods.length} mods as good'), @@ -212,7 +209,7 @@ class _ModTroubleshooterWidgetState extends State { ), ); } - + void _markSelectedAsProblem() { if (_selectedMods.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( @@ -223,14 +220,14 @@ class _ModTroubleshooterWidgetState extends State { ); return; } - + setState(() { for (final modId in _selectedMods) { _problemMods.add(modId); _checkedMods.remove(modId); } }); - + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Marked ${_selectedMods.length} mods as problematic'), @@ -239,34 +236,32 @@ class _ModTroubleshooterWidgetState extends State { ), ); } - + @override Widget build(BuildContext context) { // Make sure we're initialized if (!_isInitialized) { _initialize(); } - + if (!_isInitialized || modManager.mods.isEmpty) { return _buildEmptyState(); } - + return Column( - children: [ - _buildControlPanel(), - Expanded( - child: _buildModList(), - ), - ], + children: [_buildControlPanel(), Expanded(child: _buildModList())], ); } - + Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.build, size: AppThemeExtension.of(context).iconSizeLarge * 2), + Icon( + Icons.build, + size: AppThemeExtension.of(context).iconSizeLarge * 2, + ), const SizedBox(height: 16), Text( 'Troubleshooting', @@ -298,57 +293,7 @@ class _ModTroubleshooterWidgetState extends State { ), ); } - - Widget _buildInstructionsPanel() { - return Card( - margin: AppThemeExtension.of(context).paddingRegular, - child: Padding( - padding: AppThemeExtension.of(context).paddingRegular, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Mod Troubleshooter', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 8), - Text( - 'This tool helps you find problematic mods by testing different combinations. ' - 'Follow these steps:', - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 8), - Text( - '1. Start RimWorld with the highlighted mods active', - style: Theme.of(context).textTheme.bodyMedium, - ), - Text( - '2. If the game works correctly, mark those mods as "Good"', - style: Theme.of(context).textTheme.bodyMedium, - ), - Text( - '3. If the problem occurs, use "Forward" or "Backward" to narrow down the problem', - style: Theme.of(context).textTheme.bodyMedium, - ), - Text( - '4. Mods marked as "Problem" are more likely to be causing issues', - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 8), - if (_selectedMods.isNotEmpty) - Text( - 'Currently testing ${_selectedMods.length} mods', - style: TextStyle( - fontWeight: FontWeight.bold, - color: AppThemeExtension.of(context).enabledModColor, - ), - ), - ], - ), - ), - ); - } - + Widget _buildControlPanel() { return Card( margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), @@ -362,7 +307,7 @@ class _ModTroubleshooterWidgetState extends State { // Compact instruction Expanded( child: Text( - _selectedMods.isNotEmpty + _selectedMods.isNotEmpty ? 'Testing ${_selectedMods.length} mods. Tap highlighted mods to navigate. Mark results below:' : 'Click highlighted mods to begin testing. Blue→forward, purple←backward.', style: TextStyle( @@ -373,16 +318,13 @@ class _ModTroubleshooterWidgetState extends State { ), ], ), - + const SizedBox(height: 8), - + Row( children: [ // Binary/Linear mode toggle - Text( - 'Mode:', - style: Theme.of(context).textTheme.bodyMedium, - ), + Text('Mode:', style: Theme.of(context).textTheme.bodyMedium), const SizedBox(width: 8), ToggleButtons( isSelected: [!_isBinaryMode, _isBinaryMode], @@ -403,14 +345,11 @@ class _ModTroubleshooterWidgetState extends State { ), ], ), - + // Step size input field (only for linear mode) if (!_isBinaryMode) ...[ const SizedBox(width: 16), - Text( - 'Step:', - style: Theme.of(context).textTheme.bodyMedium, - ), + Text('Step:', style: Theme.of(context).textTheme.bodyMedium), const SizedBox(width: 4), SizedBox( width: 60, @@ -436,41 +375,58 @@ class _ModTroubleshooterWidgetState extends State { ), ), ], - + const Spacer(), - + // Buttons to mark selected mods if (_selectedMods.isNotEmpty) ...[ OutlinedButton.icon( - icon: Icon(Icons.error, color: Colors.red.shade300, size: 16), + icon: Icon( + Icons.error, + color: Colors.red.shade300, + size: 16, + ), label: const Text('Problem'), style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 0, + ), ), onPressed: _markSelectedAsProblem, ), const SizedBox(width: 4), OutlinedButton.icon( - icon: Icon(Icons.check_circle, color: Colors.green.shade300, size: 16), + icon: Icon( + Icons.check_circle, + color: Colors.green.shade300, + size: 16, + ), label: const Text('Good'), style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 0, + ), ), onPressed: _markSelectedAsGood, ), const SizedBox(width: 4), ], - + // Reset button OutlinedButton.icon( icon: const Icon(Icons.refresh, size: 16), label: const Text('Reset'), style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 0, + ), ), onPressed: _resetTroubleshooter, ), - + if (_selectedMods.isNotEmpty) ...[ const SizedBox(width: 4), // Save config button @@ -478,7 +434,10 @@ class _ModTroubleshooterWidgetState extends State { icon: const Icon(Icons.save, size: 16), label: const Text('Save'), style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 0, + ), ), onPressed: _saveTroubleshootingConfig, ), @@ -490,11 +449,11 @@ class _ModTroubleshooterWidgetState extends State { ), ); } - + Widget _buildModList() { // Get the original mod order from mod manager final fullModList = modManager.activeMods.keys.toList(); - + return Card( margin: AppThemeExtension.of(context).paddingRegular, child: Column( @@ -529,28 +488,32 @@ class _ModTroubleshooterWidgetState extends State { itemBuilder: (context, index) { final modId = fullModList[index]; final mod = modManager.mods[modId]; - + if (mod == null) return const SizedBox.shrink(); - + // Determine if this mod is in the selection range for highlighted navigation final bool isSelected = _selectedMods.contains(modId); - + // Check if this mod would be included in the next Forward/Backward move bool isInNextForward = false; bool isInNextBackward = false; - - if (_nextForwardMove != null && index >= _nextForwardMove!.startIndex && index < _nextForwardMove!.endIndex) { + + if (_nextForwardMove != null && + index >= _nextForwardMove!.startIndex && + index < _nextForwardMove!.endIndex) { isInNextForward = true; } - - if (_nextBackwardMove != null && index >= _nextBackwardMove!.startIndex && index < _nextBackwardMove!.endIndex) { + + if (_nextBackwardMove != null && + index >= _nextBackwardMove!.startIndex && + index < _nextBackwardMove!.endIndex) { isInNextBackward = true; } - + // Determine mod status for coloring final bool isChecked = _checkedMods.contains(modId); final bool isProblem = _problemMods.contains(modId); - + return GestureDetector( onTap: () { // Navigation takes precedence if this mod is in a navigation range @@ -574,8 +537,8 @@ class _ModTroubleshooterWidgetState extends State { vertical: 4.0, ), color: _getModCardColor( - isSelected: isSelected, - isChecked: isChecked, + isSelected: isSelected, + isChecked: isChecked, isProblem: isProblem, isInNextForward: isInNextForward, isInNextBackward: isInNextBackward, @@ -592,51 +555,75 @@ class _ModTroubleshooterWidgetState extends State { children: [ if (isSelected) Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 0, + ), margin: const EdgeInsets.only(right: 4), decoration: BoxDecoration( - color: Colors.blue.shade800.withOpacity(0.2), + color: const Color( + 0x28303F9F, + ), // Blue with alpha 40 borderRadius: BorderRadius.circular(4), ), child: Text( 'TESTING', style: TextStyle( color: Colors.blue.shade200, - fontSize: AppThemeExtension.of(context).textSizeSmall, + fontSize: + AppThemeExtension.of( + context, + ).textSizeSmall, fontWeight: FontWeight.bold, ), ), ), if (isChecked) Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 0, + ), margin: const EdgeInsets.only(right: 4), decoration: BoxDecoration( - color: Colors.green.shade800.withOpacity(0.2), + color: const Color( + 0x1E2E7D32, + ), // Green with alpha 30 borderRadius: BorderRadius.circular(4), ), child: Text( 'GOOD', style: TextStyle( color: Colors.green.shade200, - fontSize: AppThemeExtension.of(context).textSizeSmall, + fontSize: + AppThemeExtension.of( + context, + ).textSizeSmall, fontWeight: FontWeight.bold, ), ), ), if (isProblem) Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 0, + ), margin: const EdgeInsets.only(right: 4), decoration: BoxDecoration( - color: Colors.red.shade800.withOpacity(0.2), + color: const Color( + 0x1EC62828, + ), // Red with alpha 30 borderRadius: BorderRadius.circular(4), ), child: Text( 'PROBLEM', style: TextStyle( color: Colors.red.shade200, - fontSize: AppThemeExtension.of(context).textSizeSmall, + fontSize: + AppThemeExtension.of( + context, + ).textSizeSmall, fontWeight: FontWeight.bold, ), ), @@ -645,7 +632,10 @@ class _ModTroubleshooterWidgetState extends State { child: Text( mod.name, style: TextStyle( - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + fontWeight: + isSelected + ? FontWeight.bold + : FontWeight.normal, ), ), ), @@ -666,8 +656,10 @@ class _ModTroubleshooterWidgetState extends State { message: 'Base Game', child: Icon( Icons.home, - color: AppThemeExtension.of(context).baseGameColor, - size: AppThemeExtension.of(context).iconSizeSmall, + color: + AppThemeExtension.of(context).baseGameColor, + size: + AppThemeExtension.of(context).iconSizeSmall, ), ), if (mod.isExpansion) @@ -675,20 +667,26 @@ class _ModTroubleshooterWidgetState extends State { message: 'Expansion', child: Icon( Icons.star, - color: AppThemeExtension.of(context).expansionColor, - size: AppThemeExtension.of(context).iconSizeSmall, + color: + AppThemeExtension.of( + context, + ).expansionColor, + size: + AppThemeExtension.of(context).iconSizeSmall, ), ), if (mod.dependencies.isNotEmpty) Tooltip( - message: 'Dependencies:\n${mod.dependencies.join('\n')}', + message: + 'Dependencies:\n${mod.dependencies.join('\n')}', child: Icon( Icons.link, color: AppThemeExtension.of(context).linkColor, - size: AppThemeExtension.of(context).iconSizeSmall, + size: + AppThemeExtension.of(context).iconSizeSmall, ), ), - + // Display status icon if (isChecked) Tooltip( @@ -706,63 +704,87 @@ class _ModTroubleshooterWidgetState extends State { color: Colors.red.shade300, ), ), - + const SizedBox(width: 4), - + // Show navigation indicators if (isInNextForward) Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ), decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.2), + color: const Color( + 0x0A2196F3, + ), // Blue with alpha 10 borderRadius: BorderRadius.circular(4), ), child: Tooltip( - message: 'Click to move Forward (test this mod)', + message: + 'Click to move Forward (test this mod)', child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.arrow_forward, color: Colors.blue.shade300, - size: AppThemeExtension.of(context).iconSizeSmall, + size: + AppThemeExtension.of( + context, + ).iconSizeSmall, ), const SizedBox(width: 2), Text( 'Forward', style: TextStyle( color: Colors.blue.shade300, - fontSize: AppThemeExtension.of(context).textSizeSmall, + fontSize: + AppThemeExtension.of( + context, + ).textSizeSmall, ), ), ], ), ), ), - + if (isInNextBackward) Container( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 2, + ), decoration: BoxDecoration( - color: Colors.purple.withOpacity(0.2), + color: const Color( + 0x0A9C27B0, + ), // Purple with alpha 10 borderRadius: BorderRadius.circular(4), ), child: Tooltip( - message: 'Click to move Backward (test this mod)', + message: + 'Click to move Backward (test this mod)', child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.arrow_back, color: Colors.purple.shade300, - size: AppThemeExtension.of(context).iconSizeSmall, + size: + AppThemeExtension.of( + context, + ).iconSizeSmall, ), const SizedBox(width: 2), Text( 'Back', style: TextStyle( color: Colors.purple.shade300, - fontSize: AppThemeExtension.of(context).textSizeSmall, + fontSize: + AppThemeExtension.of( + context, + ).textSizeSmall, ), ), ], @@ -781,7 +803,7 @@ class _ModTroubleshooterWidgetState extends State { ), ); } - + Color _getModCardColor({ required bool isSelected, required bool isChecked, @@ -791,22 +813,19 @@ class _ModTroubleshooterWidgetState extends State { }) { // Priority: 1. Selected, 2. Navigation areas, 3. Status if (isSelected) { - return Colors.blue.shade800.withOpacity(0.3); + return const Color(0x80303F9F); } else if (isInNextForward && isInNextBackward) { - // Both forward and backward - more obvious purple - return Colors.deepPurple.withOpacity(0.3); + return const Color(0x50673AB7); } else if (isInNextForward) { - // Forward navigation - more obvious blue - return Colors.blue.withOpacity(0.25); + return const Color(0x402196F3); } else if (isInNextBackward) { - // Backward navigation - more obvious purple - return Colors.purple.withOpacity(0.25); + return const Color(0x409C27B0); } else if (isChecked) { - return Colors.green.shade800.withOpacity(0.2); + return const Color(0x802E7D32); } else if (isProblem) { - return Colors.red.shade800.withOpacity(0.2); + return const Color(0x80C62828); } - + return Colors.transparent; } -} \ No newline at end of file +}