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