From 2228aeeafa5b8ce939b852f4a6e60f76ae963d50 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sat, 15 Mar 2025 22:50:41 +0100 Subject: [PATCH] Implement incremental loading via streaming --- lib/main.dart | 143 ++++++++++++++++++++++++++++++++++++++++++--- lib/modloader.dart | 8 ++- 2 files changed, 142 insertions(+), 9 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index e1d5dd2..026d0e8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,15 @@ import 'package:flutter/material.dart'; import 'package:rimworld_modman/modloader.dart'; +// Global variable to store loaded mods for access across the app +late ModList modManager; + void main() { - ModList(path: modsRoot).load(); - ConfigFile(path: configPath).load(); + modManager = ModList(path: modsRoot); + modManager.load().listen((mod) { + print(mod); + }); + // runApp(const RimWorldModManager()); } @@ -61,7 +67,7 @@ class _ModManagerHomePageState extends State { }); }, items: const [ - BottomNavigationBarItem(icon: Icon(Icons.list), label: 'Mods'), + BottomNavigationBarItem(icon: Icon(Icons.extension), label: 'Mods'), BottomNavigationBarItem( icon: Icon(Icons.reorder), label: 'Load Order', @@ -77,11 +83,30 @@ class _ModManagerHomePageState extends State { } // Page to display all installed mods with enable/disable toggles -class ModListPage extends StatelessWidget { +class ModListPage extends StatefulWidget { const ModListPage({super.key}); + @override + State createState() => _ModListPageState(); +} + +class _ModListPageState extends State { + final List _loadedMods = []; + bool _isLoading = false; + String _loadingStatus = ''; + int _totalModsFound = 0; + @override Widget build(BuildContext context) { + return Scaffold( + body: + _loadedMods.isEmpty && !_isLoading + ? _buildEmptyState() + : _buildModList(), + ); + } + + Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -91,20 +116,122 @@ class ModListPage extends StatelessWidget { Text('Mod List', style: Theme.of(context).textTheme.headlineMedium), const SizedBox(height: 16), Text( - 'Here you\'ll see your installed mods.', + 'Ready to scan for RimWorld mods.', style: Theme.of(context).textTheme.bodyLarge, ), const SizedBox(height: 24), ElevatedButton( - onPressed: () { - // TODO: Implement mod scanning functionality - }, + onPressed: _startLoadingMods, child: const Text('Scan for Mods'), ), ], ), ); } + + Widget _buildModList() { + return Column( + children: [ + if (_isLoading) + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + LinearProgressIndicator( + value: + _totalModsFound > 0 + ? _loadedMods.length / _totalModsFound + : null, + ), + const SizedBox(height: 8), + Text( + _loadingStatus, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: _loadedMods.length, + itemBuilder: (context, index) { + final mod = _loadedMods[index]; + return Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: ListTile( + title: Text(mod.name), + subtitle: Text( + 'ID: ${mod.id}\nSize: ${mod.size} files', + style: Theme.of(context).textTheme.bodySmall, + ), + trailing: Icon( + Icons.circle, + color: + mod.hardDependencies.isNotEmpty + ? Colors.orange + : Colors.green, + size: 12, + ), + onTap: () { + // TODO: Show mod details + }, + ), + ); + }, + ), + ), + if (!_isLoading && _loadedMods.isNotEmpty) + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${_loadedMods.length} mods loaded'), + ElevatedButton( + onPressed: _startLoadingMods, + child: const Text('Reload'), + ), + ], + ), + ), + ], + ); + } + + void _startLoadingMods() { + setState(() { + _loadedMods.clear(); + _isLoading = true; + _loadingStatus = 'Scanning for mods...'; + }); + + // Use batch loading for better performance + modManager + .load() + .listen( + (mod) { + setState(() { + _loadedMods.add(mod); + _loadingStatus = 'Loaded ${_loadedMods.length} mods...'; + }); + }, + onError: (error) { + setState(() { + _isLoading = false; + _loadingStatus = 'Error loading mods: $error'; + }); + }, + onDone: () { + setState(() { + _isLoading = false; + _loadingStatus = 'Completed! ${_loadedMods.length} mods loaded.'; + + // Sort mods by name for better display + _loadedMods.sort((a, b) => a.name.compareTo(b.name)); + }); + }, + ); + } } // Page to manage mod load order with dependency visualization diff --git a/lib/modloader.dart b/lib/modloader.dart index 8a4dca7..ff55429 100644 --- a/lib/modloader.dart +++ b/lib/modloader.dart @@ -177,7 +177,7 @@ class ModList { ModList({required this.path}); - void load() { + Stream load() async* { final directory = Directory(path); print('Loading configuration from: $path'); @@ -187,8 +187,12 @@ class ModList { entities.whereType().map((dir) => dir.path).toList(); print('Found ${modDirectories.length} mod directories:'); + for (final modDir in modDirectories) { try { + // Add a small delay to prevent UI freezing and give time for rendering + await Future.delayed(const Duration(milliseconds: 5)); + final mod = Mod.fromDirectory(modDir); mods[mod.id] = mod; print( @@ -198,6 +202,8 @@ class ModList { 'Soft Dependencies: ${mod.softDependencies.join(', ')}, ' 'Incompatibilities: ${mod.incompatabilities.join(', ')}', ); + + yield mod; print('Current total mods loaded: ${mods.length}'); } catch (e) { print('Error loading mod from directory: $modDir');