Skip to content

commit d642f3c

abduznik edited this page May 23, 2026 · 1 revision

feat: add scanning UI indicator and safe filename fallback for matching

Commit: d642f3c32c06fc683a5e6dcd96255bb6a9a9eddb

Author: abduznik

Date: 2026-04-28

Why: Adds a new feature or capability to the application.

Files Changed

lib/core/romm/rom_scanner_service.dart             | 16 ++++++-
 lib/providers/downloaded_games_cache_provider.dart |  4 ++
 lib/ui/screens/library_screen.dart                 | 51 +++++++++++++++-------
 3 files changed, 54 insertions(+), 17 deletions(-)
  • lib/core/romm/rom_scanner_service.dart
  • lib/providers/downloaded_games_cache_provider.dart
  • lib/ui/screens/library_screen.dart

Diff

diff --git a/lib/core/romm/rom_scanner_service.dart b/lib/core/romm/rom_scanner_service.dart
index a8db14e..6a7a83e 100644
--- a/lib/core/romm/rom_scanner_service.dart
+++ b/lib/core/romm/rom_scanner_service.dart
@@ -133,12 +133,26 @@ class RomScannerService {
       final results = await Future.wait(chunk.map((filePath) async {
         final fileName = p.basename(filePath);
         try {
-          final searchResult = await _rommService.searchRoms(search: fileName);
+          // Try 1: Exact filename match (safest)
+          var searchResult = await _rommService.searchRoms(search: fileName);
           if (searchResult.isNotEmpty) {
             final game = searchResult.first;
             await _mappingService.updateMapping(filePath, game.id);
             return RomSyncResult(filePath, game.id, game: game);
           }
+
+          // Try 2: SAFE FALLBACK - Strip extension
+          // This helps with titles like "Marvel: Ultimate Alliance" where the file is "Marvel Ultimate Alliance.cso"
+          final nameWithoutExt = p.basenameWithoutExtension(fileName).trim();
+          if (nameWithoutExt.isNotEmpty && nameWithoutExt != fileName) {
+            debugPrint('[RomScanner] Exact match failed, trying without extension: $nameWithoutExt');
+            searchResult = await _rommService.searchRoms(search: nameWithoutExt);
+            if (searchResult.isNotEmpty) {
+              final game = searchResult.first;
+              await _mappingService.updateMapping(filePath, game.id);
+              return RomSyncResult(filePath, game.id, game: game);
+            }
+          }
         } catch (e) {
           debugPrint('[RomScanner] Error matching $fileName: $e');
         }
diff --git a/lib/providers/downloaded_games_cache_provider.dart b/lib/providers/downloaded_games_cache_provider.dart
index 69a7fa2..8a2dc35 100644
--- a/lib/providers/downloaded_games_cache_provider.dart
+++ b/lib/providers/downloaded_games_cache_provider.dart
@@ -6,6 +6,8 @@ import 'dart:async';
 import 'dart:io' as io;
 import 'package:flutter/foundation.dart';
 
+final isScanningProvider = StateProvider<bool>((ref) => false);
+
 class DownloadedGamesCache extends StateNotifier<Map<String, bool>> {
   final Ref _ref;
   bool _isSyncing = false;
@@ -100,6 +102,7 @@ class DownloadedGamesCache extends StateNotifier<Map<String, bool>> {
     }
 
     _isSyncing = true;
+    _ref.read(isScanningProvider.notifier).state = true;
     debugPrint('[DownloadedGamesCache] Starting incremental sync...');
 
     try {
@@ -148,6 +151,7 @@ class DownloadedGamesCache extends StateNotifier<Map<String, bool>> {
       debugPrint('[DownloadedGamesCache] Sync error: $e');
     } finally {
       _isSyncing = false;
+      _ref.read(isScanningProvider.notifier).state = false;
       debugPrint('[DownloadedGamesCache] Incremental sync complete.');
       // Final refresh to ensure everything is flushed and matched up
       await refresh();
diff --git a/lib/ui/screens/library_screen.dart b/lib/ui/screens/library_screen.dart
index cf4c147..6c6fdd7 100644
--- a/lib/ui/screens/library_screen.dart
+++ b/lib/ui/screens/library_screen.dart
@@ -310,26 +310,45 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> with LibraryActio
           Consumer(
             builder: (context, ref, _) {
               final hasFilters = ref.watch(activeFiltersProvider).hasActiveFilters;
-              return Stack(
+              final isScanning = ref.watch(isScanningProvider);
+              
+              return Row(
+                mainAxisSize: MainAxisSize.min,
                 children: [
-                  IconButton(
-                    icon: const Icon(Icons.tune),
-                    tooltip: 'Filter',
-                    onPressed: () => _openFilterSheet(context, ref),
-                  ),
-                  if (hasFilters)
-                    Positioned(
-                      right: 8,
-                      top: 8,
-                      child: Container(
-                        width: 8,
-                        height: 8,
-                        decoration: const BoxDecoration(
-                          color: Colors.deepPurple,
-                          shape: BoxShape.circle,
+                  if (isScanning)
+                    const Padding(
+                      padding: EdgeInsets.only(right: 4.0),
+                      child: SizedBox(
+                        width: 16,
+                        height: 16,
+                        child: CircularProgressIndicator(
+                          strokeWidth: 2,
+                          valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
                         ),
                       ),
                     ),
+                  Stack(
+                    children: [
+                      IconButton(
+                        icon: const Icon(Icons.tune),
+                        tooltip: 'Filter',
+                        onPressed: () => _openFilterSheet(context, ref),
+                      ),
+                      if (hasFilters)
+                        Positioned(
+                          right: 8,
+                          top: 8,
+                          child: Container(
+                            width: 8,
+                            height: 8,
+                            decoration: const BoxDecoration(
+                              color: Colors.deepPurple,
+                              shape: BoxShape.circle,
+                            ),
+                          ),
+                        ),
+                    ],
+                  ),
                 ],
               );
             },

Clone this wiki locally