Fix up some of the todos
This commit is contained in:
135
lib/main.dart
135
lib/main.dart
@@ -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,10 +405,33 @@ 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
|
if (_searchQuery.isEmpty) {
|
||||||
|
filteredAvailableMods = _availableMods;
|
||||||
|
filteredActiveMods = _activeMods;
|
||||||
|
} else {
|
||||||
|
if (_useRegex && _searchRegex != null) {
|
||||||
|
// Use regex pattern for filtering
|
||||||
|
filteredAvailableMods = _availableMods
|
||||||
|
.where(
|
||||||
|
(mod) =>
|
||||||
|
_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(
|
.where(
|
||||||
(mod) =>
|
(mod) =>
|
||||||
mod.name.toLowerCase().contains(_searchQuery) ||
|
mod.name.toLowerCase().contains(_searchQuery) ||
|
||||||
@@ -420,16 +439,15 @@ class _ModManagerPageState extends State<ModManagerPage> {
|
|||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final filteredActiveMods =
|
filteredActiveMods = _activeMods
|
||||||
_searchQuery.isEmpty
|
|
||||||
? _activeMods
|
|
||||||
: _activeMods
|
|
||||||
.where(
|
.where(
|
||||||
(mod) =>
|
(mod) =>
|
||||||
mod.name.toLowerCase().contains(_searchQuery) ||
|
mod.name.toLowerCase().contains(_searchQuery) ||
|
||||||
mod.id.toLowerCase().contains(_searchQuery),
|
mod.id.toLowerCase().contains(_searchQuery),
|
||||||
)
|
)
|
||||||
.toList();
|
.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: () {
|
||||||
|
@@ -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]) {
|
||||||
|
Reference in New Issue
Block a user