Implement mod troubleshooter
This commit is contained in:
812
lib/mod_troubleshooter_widget.dart
Normal file
812
lib/mod_troubleshooter_widget.dart
Normal file
@@ -0,0 +1,812 @@
|
||||
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
|
||||
/// - Adjust step size for linear navigation
|
||||
/// - Mark mods as checked/good or problematic
|
||||
/// - Find specific mods causing issues in their load order
|
||||
class ModTroubleshooterWidget extends StatefulWidget {
|
||||
const ModTroubleshooterWidget({super.key});
|
||||
|
||||
@override
|
||||
State<ModTroubleshooterWidget> createState() => _ModTroubleshooterWidgetState();
|
||||
}
|
||||
|
||||
class _ModTroubleshooterWidgetState extends State<ModTroubleshooterWidget> {
|
||||
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<String> _checkedMods = {};
|
||||
|
||||
// Set of mod IDs that are suspected to cause issues
|
||||
final Set<String> _problemMods = {};
|
||||
|
||||
// The currently selected mod IDs (for highlighting)
|
||||
List<String> _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);
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateForward() {
|
||||
ModList result;
|
||||
if (_isBinaryMode) {
|
||||
result = _troubleshooter.binaryForward();
|
||||
} 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
|
||||
setState(() {
|
||||
_selectedMods = loadOrder.loadOrder;
|
||||
_updateNextMoves();
|
||||
});
|
||||
}
|
||||
|
||||
void _navigateBackward() {
|
||||
ModList result;
|
||||
if (_isBinaryMode) {
|
||||
result = _troubleshooter.binaryBackward();
|
||||
} else {
|
||||
result = _troubleshooter.linearBackward(stepSize: _stepSize);
|
||||
}
|
||||
|
||||
// Load all required dependencies for the selected mods
|
||||
final loadOrder = result.loadRequired();
|
||||
|
||||
// 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();
|
||||
_problemMods.clear();
|
||||
_isInitialized = false;
|
||||
});
|
||||
_initialize();
|
||||
}
|
||||
|
||||
void _saveTroubleshootingConfig() {
|
||||
// Only save if we have a valid selection
|
||||
if (_selectedMods.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('No mods selected to save'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
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.'),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _markSelectedAsGood() {
|
||||
if (_selectedMods.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('No mods selected to mark'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
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'),
|
||||
backgroundColor: Colors.green,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _markSelectedAsProblem() {
|
||||
if (_selectedMods.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('No mods selected to mark'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
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'),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.build, size: AppThemeExtension.of(context).iconSizeLarge * 2),
|
||||
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(
|
||||
'Load mods first to use the troubleshooting tools.',
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// No direct access to the ModManagerHomePage state, so just show a message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Please go to the Mods tab to load mods first'),
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Go to Mod Manager'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
child: Padding(
|
||||
padding: AppThemeExtension.of(context).paddingSmall,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Compact instruction
|
||||
Expanded(
|
||||
child: Text(
|
||||
_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(
|
||||
fontSize: AppThemeExtension.of(context).textSizeRegular,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
// Binary/Linear mode toggle
|
||||
Text(
|
||||
'Mode:',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ToggleButtons(
|
||||
isSelected: [!_isBinaryMode, _isBinaryMode],
|
||||
onPressed: (index) {
|
||||
setState(() {
|
||||
_isBinaryMode = index == 1;
|
||||
_updateNextMoves();
|
||||
});
|
||||
},
|
||||
children: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text('Linear'),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text('Binary'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Step size input field (only for linear mode)
|
||||
if (!_isBinaryMode) ...[
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Step:',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 6,
|
||||
),
|
||||
),
|
||||
controller: _stepSizeController,
|
||||
onChanged: (value) {
|
||||
final parsedValue = int.tryParse(value);
|
||||
if (parsedValue != null && parsedValue > 0) {
|
||||
setState(() {
|
||||
_stepSize = parsedValue;
|
||||
_updateNextMoves();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Buttons to mark selected mods
|
||||
if (_selectedMods.isNotEmpty) ...[
|
||||
OutlinedButton.icon(
|
||||
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),
|
||||
),
|
||||
onPressed: _markSelectedAsProblem,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
OutlinedButton.icon(
|
||||
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),
|
||||
),
|
||||
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),
|
||||
),
|
||||
onPressed: _resetTroubleshooter,
|
||||
),
|
||||
|
||||
if (_selectedMods.isNotEmpty) ...[
|
||||
const SizedBox(width: 4),
|
||||
// Save config button
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.save, size: 16),
|
||||
label: const Text('Save'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
|
||||
),
|
||||
onPressed: _saveTroubleshootingConfig,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).primaryColor,
|
||||
padding: AppThemeExtension.of(context).paddingRegular,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Active Mods (${fullModList.length})',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (_nextForwardMove != null || _nextBackwardMove != null)
|
||||
Text(
|
||||
'Click ↓blue areas to move forward, ↑purple to move backward',
|
||||
style: TextStyle(
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: fullModList.length,
|
||||
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) {
|
||||
isInNextForward = true;
|
||||
}
|
||||
|
||||
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
|
||||
if (isInNextForward) {
|
||||
_navigateForward();
|
||||
} else if (isInNextBackward) {
|
||||
_navigateBackward();
|
||||
}
|
||||
// Otherwise toggle the status of this mod
|
||||
else if (isChecked) {
|
||||
_markAsProblem(modId);
|
||||
} else if (isProblem) {
|
||||
_clearMarks(modId);
|
||||
} else {
|
||||
_markAsGood(modId);
|
||||
}
|
||||
},
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
color: _getModCardColor(
|
||||
isSelected: isSelected,
|
||||
isChecked: isChecked,
|
||||
isProblem: isProblem,
|
||||
isInNextForward: isInNextForward,
|
||||
isInNextBackward: isInNextBackward,
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Text(
|
||||
'${index + 1}',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSelected ? Colors.white : Colors.grey,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
if (isSelected)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade800.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'TESTING',
|
||||
style: TextStyle(
|
||||
color: Colors.blue.shade200,
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isChecked)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade800.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'GOOD',
|
||||
style: TextStyle(
|
||||
color: Colors.green.shade200,
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isProblem)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 0),
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade800.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'PROBLEM',
|
||||
style: TextStyle(
|
||||
color: Colors.red.shade200,
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
mod.name,
|
||||
style: TextStyle(
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
modId,
|
||||
style: TextStyle(
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Display mod characteristics
|
||||
if (mod.isBaseGame)
|
||||
Tooltip(
|
||||
message: 'Base Game',
|
||||
child: Icon(
|
||||
Icons.home,
|
||||
color: AppThemeExtension.of(context).baseGameColor,
|
||||
size: AppThemeExtension.of(context).iconSizeSmall,
|
||||
),
|
||||
),
|
||||
if (mod.isExpansion)
|
||||
Tooltip(
|
||||
message: 'Expansion',
|
||||
child: Icon(
|
||||
Icons.star,
|
||||
color: AppThemeExtension.of(context).expansionColor,
|
||||
size: AppThemeExtension.of(context).iconSizeSmall,
|
||||
),
|
||||
),
|
||||
if (mod.dependencies.isNotEmpty)
|
||||
Tooltip(
|
||||
message: 'Dependencies:\n${mod.dependencies.join('\n')}',
|
||||
child: Icon(
|
||||
Icons.link,
|
||||
color: AppThemeExtension.of(context).linkColor,
|
||||
size: AppThemeExtension.of(context).iconSizeSmall,
|
||||
),
|
||||
),
|
||||
|
||||
// Display status icon
|
||||
if (isChecked)
|
||||
Tooltip(
|
||||
message: 'Marked as working correctly',
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green.shade300,
|
||||
),
|
||||
)
|
||||
else if (isProblem)
|
||||
Tooltip(
|
||||
message: 'Marked as problematic',
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
color: Colors.red.shade300,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 4),
|
||||
|
||||
// Show navigation indicators
|
||||
if (isInNextForward)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Tooltip(
|
||||
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,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'Forward',
|
||||
style: TextStyle(
|
||||
color: Colors.blue.shade300,
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (isInNextBackward)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.purple.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Tooltip(
|
||||
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,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'Back',
|
||||
style: TextStyle(
|
||||
color: Colors.purple.shade300,
|
||||
fontSize: AppThemeExtension.of(context).textSizeSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getModCardColor({
|
||||
required bool isSelected,
|
||||
required bool isChecked,
|
||||
required bool isProblem,
|
||||
required bool isInNextForward,
|
||||
required bool isInNextBackward,
|
||||
}) {
|
||||
// Priority: 1. Selected, 2. Navigation areas, 3. Status
|
||||
if (isSelected) {
|
||||
return Colors.blue.shade800.withOpacity(0.3);
|
||||
} else if (isInNextForward && isInNextBackward) {
|
||||
// Both forward and backward - more obvious purple
|
||||
return Colors.deepPurple.withOpacity(0.3);
|
||||
} else if (isInNextForward) {
|
||||
// Forward navigation - more obvious blue
|
||||
return Colors.blue.withOpacity(0.25);
|
||||
} else if (isInNextBackward) {
|
||||
// Backward navigation - more obvious purple
|
||||
return Colors.purple.withOpacity(0.25);
|
||||
} else if (isChecked) {
|
||||
return Colors.green.shade800.withOpacity(0.2);
|
||||
} else if (isProblem) {
|
||||
return Colors.red.shade800.withOpacity(0.2);
|
||||
}
|
||||
|
||||
return Colors.transparent;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user