Refactor a large part of main into theme

This commit is contained in:
2025-03-18 21:40:34 +01:00
parent 02cfe01ae0
commit 164e95fa54

View File

@@ -5,6 +5,175 @@ import 'package:rimworld_modman/logger.dart';
import 'package:rimworld_modman/mod.dart';
import 'package:rimworld_modman/mod_list.dart';
// Theme extension to store app-specific constants
class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
final double iconSizeSmall;
final double iconSizeRegular;
final double iconSizeLarge;
final double textSizeSmall;
final double textSizeRegular;
final double textSizeLarge;
final EdgeInsets paddingSmall;
final EdgeInsets paddingRegular;
final EdgeInsets paddingLarge;
final Color enabledModColor;
final Color enabledModBackgroundColor;
final Color errorBackgroundColor;
final Color warningColor;
final Color errorColor;
final Color baseGameColor;
final Color expansionColor;
final Color linkColor;
final Color loadAfterColor;
final Color loadBeforeColor;
AppThemeExtension({
required this.iconSizeSmall,
required this.iconSizeRegular,
required this.iconSizeLarge,
required this.textSizeSmall,
required this.textSizeRegular,
required this.textSizeLarge,
required this.paddingSmall,
required this.paddingRegular,
required this.paddingLarge,
required this.enabledModColor,
required this.enabledModBackgroundColor,
required this.errorBackgroundColor,
required this.warningColor,
required this.errorColor,
required this.baseGameColor,
required this.expansionColor,
required this.linkColor,
required this.loadAfterColor,
required this.loadBeforeColor,
});
static AppThemeExtension of(BuildContext context) {
return Theme.of(context).extension<AppThemeExtension>()!;
}
@override
ThemeExtension<AppThemeExtension> copyWith({
double? iconSizeSmall,
double? iconSizeRegular,
double? iconSizeLarge,
double? textSizeSmall,
double? textSizeRegular,
double? textSizeLarge,
EdgeInsets? paddingSmall,
EdgeInsets? paddingRegular,
EdgeInsets? paddingLarge,
Color? enabledModColor,
Color? enabledModBackgroundColor,
Color? errorBackgroundColor,
Color? warningColor,
Color? errorColor,
Color? baseGameColor,
Color? expansionColor,
Color? linkColor,
Color? loadAfterColor,
Color? loadBeforeColor,
}) {
return AppThemeExtension(
iconSizeSmall: iconSizeSmall ?? this.iconSizeSmall,
iconSizeRegular: iconSizeRegular ?? this.iconSizeRegular,
iconSizeLarge: iconSizeLarge ?? this.iconSizeLarge,
textSizeSmall: textSizeSmall ?? this.textSizeSmall,
textSizeRegular: textSizeRegular ?? this.textSizeRegular,
textSizeLarge: textSizeLarge ?? this.textSizeLarge,
paddingSmall: paddingSmall ?? this.paddingSmall,
paddingRegular: paddingRegular ?? this.paddingRegular,
paddingLarge: paddingLarge ?? this.paddingLarge,
enabledModColor: enabledModColor ?? this.enabledModColor,
enabledModBackgroundColor:
enabledModBackgroundColor ?? this.enabledModBackgroundColor,
errorBackgroundColor: errorBackgroundColor ?? this.errorBackgroundColor,
warningColor: warningColor ?? this.warningColor,
errorColor: errorColor ?? this.errorColor,
baseGameColor: baseGameColor ?? this.baseGameColor,
expansionColor: expansionColor ?? this.expansionColor,
linkColor: linkColor ?? this.linkColor,
loadAfterColor: loadAfterColor ?? this.loadAfterColor,
loadBeforeColor: loadBeforeColor ?? this.loadBeforeColor,
);
}
@override
ThemeExtension<AppThemeExtension> lerp(
covariant ThemeExtension<AppThemeExtension>? other,
double t,
) {
if (other is! AppThemeExtension) {
return this;
}
return AppThemeExtension(
iconSizeSmall: lerpDouble(iconSizeSmall, other.iconSizeSmall, t),
iconSizeRegular: lerpDouble(iconSizeRegular, other.iconSizeRegular, t),
iconSizeLarge: lerpDouble(iconSizeLarge, other.iconSizeLarge, t),
textSizeSmall: lerpDouble(textSizeSmall, other.textSizeSmall, t),
textSizeRegular: lerpDouble(textSizeRegular, other.textSizeRegular, t),
textSizeLarge: lerpDouble(textSizeLarge, other.textSizeLarge, t),
paddingSmall: EdgeInsets.lerp(paddingSmall, other.paddingSmall, t)!,
paddingRegular: EdgeInsets.lerp(paddingRegular, other.paddingRegular, t)!,
paddingLarge: EdgeInsets.lerp(paddingLarge, other.paddingLarge, t)!,
enabledModColor: Color.lerp(enabledModColor, other.enabledModColor, t)!,
enabledModBackgroundColor:
Color.lerp(
enabledModBackgroundColor,
other.enabledModBackgroundColor,
t,
)!,
errorBackgroundColor:
Color.lerp(errorBackgroundColor, other.errorBackgroundColor, t)!,
warningColor: Color.lerp(warningColor, other.warningColor, t)!,
errorColor: Color.lerp(errorColor, other.errorColor, t)!,
baseGameColor: Color.lerp(baseGameColor, other.baseGameColor, t)!,
expansionColor: Color.lerp(expansionColor, other.expansionColor, t)!,
linkColor: Color.lerp(linkColor, other.linkColor, t)!,
loadAfterColor: Color.lerp(loadAfterColor, other.loadAfterColor, t)!,
loadBeforeColor: Color.lerp(loadBeforeColor, other.loadBeforeColor, t)!,
);
}
static AppThemeExtension light() {
return AppThemeExtension(
iconSizeSmall: 16,
iconSizeRegular: 24,
iconSizeLarge: 32,
textSizeSmall: 10,
textSizeRegular: 14,
textSizeLarge: 18,
paddingSmall: const EdgeInsets.all(4.0),
paddingRegular: const EdgeInsets.all(8.0),
paddingLarge: const EdgeInsets.all(16.0),
enabledModColor: Colors.green,
enabledModBackgroundColor: const Color.fromRGBO(0, 128, 0, 0.1),
errorBackgroundColor: Color.fromRGBO(
Colors.red.shade900.red,
Colors.red.shade900.green,
Colors.red.shade900.blue,
0.3,
),
warningColor: Colors.orange,
errorColor: Colors.red,
baseGameColor: Colors.blue,
expansionColor: Colors.yellow,
linkColor: Colors.orange,
loadAfterColor: Colors.blue,
loadBeforeColor: Colors.green,
);
}
static AppThemeExtension dark() {
return light(); // For now, we use the same values for both light and dark
}
}
double lerpDouble(double a, double b, double t) {
return a + (b - a) * t;
}
// Constants for file paths
final String root =
Platform.isWindows
@@ -53,6 +222,7 @@ class RimWorldModManager extends StatelessWidget {
backgroundColor: Color(0xFF2A3440),
foregroundColor: Colors.white,
),
extensions: [AppThemeExtension.dark()],
),
home: const ModManagerHomePage(),
);
@@ -255,7 +425,7 @@ class _ModManagerPageState extends State<ModManagerPage> {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
padding: AppThemeExtension.of(context).paddingRegular,
child: Row(
children: [
// Search field
@@ -336,15 +506,14 @@ class _ModManagerPageState extends State<ModManagerPage> {
_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: Color.fromRGBO(
Colors.red.shade900.red,
Colors.red.shade900.green,
Colors.red.shade900.blue,
0.3
margin: EdgeInsets.symmetric(
horizontal:
AppThemeExtension.of(context).paddingRegular.horizontal,
vertical: AppThemeExtension.of(context).paddingSmall.vertical,
),
padding: AppThemeExtension.of(context).paddingRegular,
decoration: BoxDecoration(
color: AppThemeExtension.of(context).errorBackgroundColor,
borderRadius: BorderRadius.circular(4.0),
border: Border.all(color: Colors.red.shade800),
),
@@ -357,12 +526,18 @@ class _ModManagerPageState extends State<ModManagerPage> {
padding: const EdgeInsets.only(bottom: 4.0),
child: Row(
children: [
const Icon(Icons.loop, color: Colors.orange, size: 16),
Icon(
Icons.loop,
color: AppThemeExtension.of(context).warningColor,
size: AppThemeExtension.of(context).iconSizeSmall,
),
const SizedBox(width: 4),
Expanded(
child: Text(
'Dependency cycle detected: ${_cycleInfo!.join(" -> ")}',
style: const TextStyle(color: Colors.orange),
style: TextStyle(
color: AppThemeExtension.of(context).warningColor,
),
),
),
],
@@ -376,10 +551,10 @@ class _ModManagerPageState extends State<ModManagerPage> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(
Icon(
Icons.warning,
color: Colors.orange,
size: 16,
color: AppThemeExtension.of(context).warningColor,
size: AppThemeExtension.of(context).iconSizeSmall,
),
const SizedBox(width: 4),
Expanded(
@@ -388,7 +563,12 @@ class _ModManagerPageState extends State<ModManagerPage> {
children: [
Text(
'${_incompatibleMods.length} incompatible mod pairs:',
style: const TextStyle(color: Colors.orange),
style: TextStyle(
color:
AppThemeExtension.of(
context,
).warningColor,
),
),
if (_incompatibleMods.length <= 3)
...List.generate(_incompatibleMods.length, (
@@ -408,7 +588,10 @@ class _ModManagerPageState extends State<ModManagerPage> {
'$mod1$mod2',
style: TextStyle(
color: Colors.orange.shade300,
fontSize: 12,
fontSize:
AppThemeExtension.of(
context,
).textSizeSmall,
),
),
);
@@ -425,19 +608,21 @@ class _ModManagerPageState extends State<ModManagerPage> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(
Icon(
Icons.error_outline,
color: Colors.red,
size: 16,
color: AppThemeExtension.of(context).errorColor,
size: AppThemeExtension.of(context).iconSizeSmall,
),
const SizedBox(width: 4),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
Text(
'Dependency errors:',
style: TextStyle(color: Colors.red),
style: TextStyle(
color: AppThemeExtension.of(context).errorColor,
),
),
...List.generate(
_loadOrderErrors!.length > 5
@@ -452,7 +637,10 @@ class _ModManagerPageState extends State<ModManagerPage> {
'${_loadOrderErrors![index]}',
style: TextStyle(
color: Colors.red.shade300,
fontSize: 12,
fontSize:
AppThemeExtension.of(
context,
).textSizeSmall,
),
),
),
@@ -467,7 +655,10 @@ class _ModManagerPageState extends State<ModManagerPage> {
'(${_loadOrderErrors!.length - 5} more errors...)',
style: TextStyle(
color: Colors.red.shade300,
fontSize: 12,
fontSize:
AppThemeExtension.of(
context,
).textSizeSmall,
fontStyle: FontStyle.italic,
),
),
@@ -488,7 +679,7 @@ class _ModManagerPageState extends State<ModManagerPage> {
// LEFT PANEL - All available mods (alphabetical)
Expanded(
child: Card(
margin: const EdgeInsets.all(8.0),
margin: AppThemeExtension.of(context).paddingRegular,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -503,11 +694,12 @@ class _ModManagerPageState extends State<ModManagerPage> {
),
if (_searchQuery.isNotEmpty)
Padding(
padding: const EdgeInsets.all(4.0),
padding: AppThemeExtension.of(context).paddingSmall,
child: Text(
'Searching: "$_searchQuery"',
style: TextStyle(
fontSize: 12,
fontSize:
AppThemeExtension.of(context).textSizeSmall,
fontStyle: FontStyle.italic,
color: Colors.grey.shade400,
),
@@ -528,32 +720,54 @@ class _ModManagerPageState extends State<ModManagerPage> {
mod.name,
style: TextStyle(
color:
isActive ? Colors.green : Colors.white,
isActive
? AppThemeExtension.of(
context,
).enabledModColor
: Colors.white,
),
),
subtitle: Text(
'ID: ${mod.id}\nSize: ${mod.size} files',
style: Theme.of(context).textTheme.bodySmall,
),
tileColor:
isActive
? AppThemeExtension.of(
context,
).enabledModBackgroundColor
: null,
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (mod.isBaseGame)
const Tooltip(
Tooltip(
message: 'Base Game',
child: Icon(
Icons.home,
color: Colors.blue,
size: 24,
color:
AppThemeExtension.of(
context,
).baseGameColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
if (mod.isExpansion)
const Tooltip(
Tooltip(
message: 'Expansion',
child: Icon(
Icons.star,
color: Colors.yellow,
size: 24,
color:
AppThemeExtension.of(
context,
).expansionColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
const SizedBox(width: 4),
@@ -561,30 +775,48 @@ class _ModManagerPageState extends State<ModManagerPage> {
Tooltip(
message:
'Dependencies:\n${mod.dependencies.join('\n')}',
child: const Icon(
child: Icon(
Icons.link,
color: Colors.orange,
size: 24,
color:
AppThemeExtension.of(
context,
).linkColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
if (mod.loadAfter.isNotEmpty)
Tooltip(
message:
'Loads after:\n${mod.loadAfter.join('\n')}',
child: const Icon(
child: Icon(
Icons.arrow_downward,
color: Colors.blue,
size: 24,
color:
AppThemeExtension.of(
context,
).loadAfterColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
if (mod.loadBefore.isNotEmpty)
Tooltip(
message:
'Loads before:\n${mod.loadBefore.join('\n')}',
child: const Icon(
child: Icon(
Icons.arrow_upward,
color: Colors.green,
size: 24,
color:
AppThemeExtension.of(
context,
).loadBeforeColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
],
@@ -605,7 +837,7 @@ class _ModManagerPageState extends State<ModManagerPage> {
// RIGHT PANEL - Active mods (load order)
Expanded(
child: Card(
margin: const EdgeInsets.all(8.0),
margin: AppThemeExtension.of(context).paddingRegular,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -619,13 +851,14 @@ class _ModManagerPageState extends State<ModManagerPage> {
),
),
Padding(
padding: const EdgeInsets.all(8.0),
padding: AppThemeExtension.of(context).paddingRegular,
child: Text(
_searchQuery.isNotEmpty
? 'Searching: "$_searchQuery"'
: 'Larger mods are prioritized during auto-sorting.',
style: TextStyle(
fontSize: 12,
fontSize:
AppThemeExtension.of(context).textSizeSmall,
color: Colors.grey.shade400,
fontStyle: FontStyle.italic,
),
@@ -659,10 +892,10 @@ class _ModManagerPageState extends State<ModManagerPage> {
}
},
child: Card(
margin: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
margin:
AppThemeExtension.of(
context,
).paddingRegular,
child: ListTile(
leading: SizedBox(
width: 50,
@@ -680,7 +913,12 @@ class _ModManagerPageState extends State<ModManagerPage> {
decoration: BoxDecoration(
color:
_searchQuery.isNotEmpty
? const Color.fromRGBO(0, 0, 255, 0.2)
? const Color.fromRGBO(
0,
0,
255,
0.2,
)
: null,
borderRadius:
BorderRadius.circular(4),
@@ -692,7 +930,10 @@ class _ModManagerPageState extends State<ModManagerPage> {
'$actualLoadOrderPosition',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
fontSize:
AppThemeExtension.of(
context,
).textSizeSmall,
color:
_searchQuery.isNotEmpty
? Colors.blue.shade300
@@ -712,8 +953,11 @@ class _ModManagerPageState extends State<ModManagerPage> {
'This mod contains ${mod.size} files.',
child: Text(
'${mod.size}',
style: const TextStyle(
fontSize: 10,
style: TextStyle(
fontSize:
AppThemeExtension.of(
context,
).textSizeSmall,
color: Colors.grey,
),
),
@@ -727,37 +971,60 @@ class _ModManagerPageState extends State<ModManagerPage> {
title: Text(mod.name),
subtitle: Text(
mod.id,
style: const TextStyle(fontSize: 12),
style: TextStyle(
fontSize:
AppThemeExtension.of(
context,
).textSizeSmall,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (mod.isBaseGame)
const Tooltip(
Tooltip(
message: 'Base Game',
child: Icon(
Icons.home,
color: Colors.blue,
size: 24,
color:
AppThemeExtension.of(
context,
).baseGameColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
if (mod.isExpansion)
const Tooltip(
Tooltip(
message: 'Expansion',
child: Icon(
Icons.star,
color: Colors.yellow,
size: 24,
color:
AppThemeExtension.of(
context,
).expansionColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
if (mod.dependencies.isNotEmpty)
Tooltip(
message:
'Dependencies:\n${mod.dependencies.join('\n')}',
child: const Icon(
child: Icon(
Icons.link,
color: Colors.orange,
size: 24,
color:
AppThemeExtension.of(
context,
).linkColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
const SizedBox(width: 4),
@@ -765,10 +1032,16 @@ class _ModManagerPageState extends State<ModManagerPage> {
Tooltip(
message:
'Loads after other mods:\n${mod.loadAfter.join('\n')}',
child: const Icon(
child: Icon(
Icons.arrow_downward,
color: Colors.blue,
size: 24,
color:
AppThemeExtension.of(
context,
).loadAfterColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
const SizedBox(width: 4),
@@ -776,10 +1049,16 @@ class _ModManagerPageState extends State<ModManagerPage> {
Tooltip(
message:
'Loads before other mods:\n${mod.loadBefore.join('\n')}',
child: const Icon(
child: Icon(
Icons.arrow_upward,
color: Colors.green,
size: 24,
color:
AppThemeExtension.of(
context,
).loadBeforeColor,
size:
AppThemeExtension.of(
context,
).iconSizeRegular,
),
),
],