Fix up some of the todos

This commit is contained in:
2025-03-22 00:00:31 +01:00
parent 573ad05514
commit 1bb8ed9084
2 changed files with 191 additions and 33 deletions

View File

@@ -1,8 +1,6 @@
import 'dart:io'; import 'dart:io';
// TODO: Fix "load dependencies", it causes fake errors between expansions and base game // TODO: Fix "load dependencies", it causes fake errors between expansions and base game
// TODO: Add an icon for incompatibilities on the mod list
// TODO: Get rid of the demo button WHAT THE FUCK IS THE DEMO BUTTON??
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rimworld_modman/logger.dart'; import 'package:rimworld_modman/logger.dart';
import 'package:rimworld_modman/components/html_tooltip.dart'; import 'package:rimworld_modman/components/html_tooltip.dart';
@@ -31,6 +29,7 @@ class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
final Color linkColor; final Color linkColor;
final Color loadAfterColor; final Color loadAfterColor;
final Color loadBeforeColor; final Color loadBeforeColor;
final Color incompatibleColor;
AppThemeExtension({ AppThemeExtension({
required this.iconSizeSmall, required this.iconSizeSmall,
@@ -52,6 +51,7 @@ class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
required this.linkColor, required this.linkColor,
required this.loadAfterColor, required this.loadAfterColor,
required this.loadBeforeColor, required this.loadBeforeColor,
required this.incompatibleColor,
}); });
static AppThemeExtension of(BuildContext context) { static AppThemeExtension of(BuildContext context) {
@@ -79,6 +79,7 @@ class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
Color? linkColor, Color? linkColor,
Color? loadAfterColor, Color? loadAfterColor,
Color? loadBeforeColor, Color? loadBeforeColor,
Color? incompatibleColor,
}) { }) {
return AppThemeExtension( return AppThemeExtension(
iconSizeSmall: iconSizeSmall ?? this.iconSizeSmall, iconSizeSmall: iconSizeSmall ?? this.iconSizeSmall,
@@ -101,6 +102,7 @@ class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
linkColor: linkColor ?? this.linkColor, linkColor: linkColor ?? this.linkColor,
loadAfterColor: loadAfterColor ?? this.loadAfterColor, loadAfterColor: loadAfterColor ?? this.loadAfterColor,
loadBeforeColor: loadBeforeColor ?? this.loadBeforeColor, loadBeforeColor: loadBeforeColor ?? this.loadBeforeColor,
incompatibleColor: incompatibleColor ?? this.incompatibleColor,
); );
} }
@@ -138,6 +140,7 @@ class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
linkColor: Color.lerp(linkColor, other.linkColor, t)!, linkColor: Color.lerp(linkColor, other.linkColor, t)!,
loadAfterColor: Color.lerp(loadAfterColor, other.loadAfterColor, t)!, loadAfterColor: Color.lerp(loadAfterColor, other.loadAfterColor, t)!,
loadBeforeColor: Color.lerp(loadBeforeColor, other.loadBeforeColor, t)!, loadBeforeColor: Color.lerp(loadBeforeColor, other.loadBeforeColor, t)!,
incompatibleColor: Color.lerp(incompatibleColor, other.incompatibleColor, t)!,
); );
} }
@@ -167,6 +170,7 @@ class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
linkColor: Colors.orange, linkColor: Colors.orange,
loadAfterColor: Colors.blue, loadAfterColor: Colors.blue,
loadBeforeColor: Colors.green, loadBeforeColor: Colors.green,
incompatibleColor: Colors.red.shade400,
); );
} }
@@ -267,10 +271,6 @@ class _ModManagerHomePageState extends State<ModManagerHomePage> {
icon: Icon(Icons.build), icon: Icon(Icons.build),
label: 'Troubleshoot', label: 'Troubleshoot',
), ),
BottomNavigationBarItem(
icon: Icon(Icons.format_paint),
label: 'Demo',
),
], ],
), ),
); );
@@ -302,6 +302,8 @@ class _ModManagerPageState extends State<ModManagerPage> {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
String _searchQuery = ''; String _searchQuery = '';
bool _useRegex = false;
RegExp? _searchRegex;
@override @override
void initState() { void initState() {
@@ -310,12 +312,6 @@ class _ModManagerPageState extends State<ModManagerPage> {
if (modManager.mods.isNotEmpty) { if (modManager.mods.isNotEmpty) {
_loadModsFromGlobalState(); _loadModsFromGlobalState();
} }
_searchController.addListener(() {
setState(() {
_searchQuery = _searchController.text.toLowerCase();
});
});
} }
@override @override
@@ -409,27 +405,49 @@ class _ModManagerPageState extends State<ModManagerPage> {
Widget _buildSplitView() { Widget _buildSplitView() {
// Filter both available and active mods based on search query // Filter both available and active mods based on search query
final filteredAvailableMods = List<Mod> filteredAvailableMods;
_searchQuery.isEmpty List<Mod> filteredActiveMods;
? _availableMods
: _availableMods
.where(
(mod) =>
mod.name.toLowerCase().contains(_searchQuery) ||
mod.id.toLowerCase().contains(_searchQuery),
)
.toList();
final filteredActiveMods = if (_searchQuery.isEmpty) {
_searchQuery.isEmpty filteredAvailableMods = _availableMods;
? _activeMods filteredActiveMods = _activeMods;
: _activeMods } else {
.where( if (_useRegex && _searchRegex != null) {
(mod) => // Use regex pattern for filtering
mod.name.toLowerCase().contains(_searchQuery) || filteredAvailableMods = _availableMods
mod.id.toLowerCase().contains(_searchQuery), .where(
) (mod) =>
.toList(); _searchRegex!.hasMatch(mod.name.toLowerCase()) ||
_searchRegex!.hasMatch(mod.id.toLowerCase()),
)
.toList();
filteredActiveMods = _activeMods
.where(
(mod) =>
_searchRegex!.hasMatch(mod.name.toLowerCase()) ||
_searchRegex!.hasMatch(mod.id.toLowerCase()),
)
.toList();
} else {
// Use simple string contains for filtering
filteredAvailableMods = _availableMods
.where(
(mod) =>
mod.name.toLowerCase().contains(_searchQuery) ||
mod.id.toLowerCase().contains(_searchQuery),
)
.toList();
filteredActiveMods = _activeMods
.where(
(mod) =>
mod.name.toLowerCase().contains(_searchQuery) ||
mod.id.toLowerCase().contains(_searchQuery),
)
.toList();
}
}
return Column( return Column(
children: [ children: [
@@ -457,6 +475,51 @@ class _ModManagerPageState extends State<ModManagerPage> {
) )
: null, : null,
), ),
onChanged: (value) {
setState(() {
_searchQuery = value.toLowerCase();
// Try to compile regex if regex mode is enabled
if (_useRegex && _searchQuery.isNotEmpty) {
try {
_searchRegex = RegExp(_searchQuery, caseSensitive: false);
} catch (e) {
// If regex is invalid, fallback to normal search
_searchRegex = null;
}
}
});
},
),
),
const SizedBox(width: 8),
// Regex toggle
Tooltip(
message: 'Use regex pattern matching',
child: Row(
children: [
Checkbox(
value: _useRegex,
onChanged: (value) {
setState(() {
_useRegex = value ?? false;
// Try to compile regex if toggled on
if (_useRegex && _searchQuery.isNotEmpty) {
try {
_searchRegex = RegExp(_searchQuery, caseSensitive: false);
} catch (e) {
// If regex fails, keep checkbox on but disable regex internally
_searchRegex = null;
}
} else {
_searchRegex = null;
}
});
},
),
const Text('Regex'),
],
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -841,6 +904,22 @@ class _ModManagerPageState extends State<ModManagerPage> {
).iconSizeRegular, ).iconSizeRegular,
), ),
), ),
if (mod.incompatibilities.isNotEmpty)
Tooltip(
message:
'Incompatible with:\n${mod.incompatibilities.join('\n')}',
child: Icon(
Icons.warning_amber_rounded,
color:
AppThemeExtension.of(
context,
).incompatibleColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
], ],
), ),
onTap: () { onTap: () {
@@ -1097,6 +1176,22 @@ class _ModManagerPageState extends State<ModManagerPage> {
).iconSizeRegular, ).iconSizeRegular,
), ),
), ),
if (mod.incompatibilities.isNotEmpty)
Tooltip(
message:
'Incompatible with:\n${mod.incompatibilities.join('\n')}',
child: Icon(
Icons.warning_amber_rounded,
color:
AppThemeExtension.of(
context,
).incompatibleColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
], ],
), ),
onTap: () { onTap: () {

View File

@@ -702,13 +702,76 @@ class ModList {
LoadOrder loadRequired([LoadOrder? loadOrder]) { LoadOrder loadRequired([LoadOrder? loadOrder]) {
loadOrder ??= LoadOrder(); loadOrder ??= LoadOrder();
final toEnable = <String>[]; final toEnable = <String>[];
final logger = Logger.instance;
// First, identify all base game and expansion mods
final baseGameIds = <String>{};
final expansionIds = <String>{};
for (final entry in mods.entries) {
if (entry.value.isBaseGame) {
baseGameIds.add(entry.key);
} else if (entry.value.isExpansion) {
expansionIds.add(entry.key);
}
}
logger.info("Base game mods: ${baseGameIds.join(', ')}");
logger.info("Expansion mods: ${expansionIds.join(', ')}");
// Load dependencies for all active mods
for (final modid in activeMods.keys) { for (final modid in activeMods.keys) {
loadDependencies(modid, loadOrder, toEnable); loadDependencies(modid, loadOrder, toEnable);
} }
// Enable all required dependencies
for (final modid in toEnable) { for (final modid in toEnable) {
setEnabled(modid, true); setEnabled(modid, true);
} }
return generateLoadOrder(loadOrder);
// Generate the load order
final newLoadOrder = generateLoadOrder(loadOrder);
// Filter out any error messages related to incompatibilities between base game and expansions
if (newLoadOrder.hasErrors) {
final filteredErrors = <String>[];
for (final error in newLoadOrder.errors) {
// Check if the error is about incompatibility
if (error.contains('Incompatibility detected:')) {
// Extract the mod IDs from the error message
final parts = error.split(' is incompatible with ');
if (parts.length == 2) {
final firstModId = parts[0].replaceAll('Incompatibility detected: ', '');
final secondModId = parts[1];
// Check if either mod is a base game or expansion
final isBaseGameOrExpansion =
baseGameIds.contains(firstModId) || baseGameIds.contains(secondModId) ||
expansionIds.contains(firstModId) || expansionIds.contains(secondModId);
// Only keep the error if it's not between base game/expansions
if (!isBaseGameOrExpansion) {
filteredErrors.add(error);
} else {
logger.info("Ignoring incompatibility between base game or expansion mods: $error");
}
} else {
// If we can't parse the error, keep it
filteredErrors.add(error);
}
} else {
// Keep non-incompatibility errors
filteredErrors.add(error);
}
}
// Replace the errors with the filtered list
newLoadOrder.errors.clear();
newLoadOrder.errors.addAll(filteredErrors);
}
return newLoadOrder;
} }
LoadOrder loadRequiredBaseGame([LoadOrder? loadOrder]) { LoadOrder loadRequiredBaseGame([LoadOrder? loadOrder]) {