Skip to content

commit 5affcc9

abduznik edited this page May 23, 2026 · 1 revision

fix(linux): stabilize storage auto-detection and improve settings UX

Commit: 5affcc9fa40d7f8b9e3dda16e44050e44f0ba343

Author: abduznik

Date: 2026-04-28

Why: Fixes a bug or regression in the existing codebase.

Files Changed

lib/core/storage/directory_service.dart | 31 +++++++++++++++++--------
 lib/ui/screens/onboarding_screen.dart   | 27 +++++++++++++---------
 lib/ui/screens/settings_screen.dart     | 40 ++++++++++++++++++++++++---------
 3 files changed, 68 insertions(+), 30 deletions(-)
  • lib/core/storage/directory_service.dart
  • lib/ui/screens/onboarding_screen.dart
  • lib/ui/screens/settings_screen.dart

Diff

diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart
index 93b4edb..3aafe63 100644
--- a/lib/core/storage/directory_service.dart
+++ b/lib/core/storage/directory_service.dart
@@ -133,21 +133,30 @@ class DirectoryService {
       // Auto-detection logic for Linux
       if (defaultTargetPlatform == TargetPlatform.linux) {
         if (linuxSyncPreset == 'auto' || linuxSyncPreset == 'default') {
-          final detectedRoot = await detectEmuDeckRoot();
-          if (detectedRoot != null) {
-            emudeckRootPath = detectedRoot;
-            linuxSyncPreset = 'emudeck';
-            _linuxStrategy = EmuDeckStrategy();
-          } else {
+          // If we are in 'auto' or 'default', try to see if EmuDeck or RetroDeck is there.
+          // BUT: If the user has already explicitly chosen a root, don't overwrite it with auto-detection.
+          if (emudeckRootPath == null) {
+            final detectedRoot = await detectEmuDeckRoot();
+            if (detectedRoot != null) {
+              emudeckRootPath = detectedRoot;
+              linuxSyncPreset = 'emudeck';
+              _linuxStrategy = EmuDeckStrategy();
+              await _prefs.setString(_linuxSyncPresetKey, 'emudeck');
+              await _prefs.setString(_emudeckRootPathKey, emudeckRootPath!);
+            }
+          } else if (linuxSyncPreset == 'default') {
+             // If they are on 'default' (manual) but have an emudeckRootPath, 
+             // we stay on 'default' unless it's the very first run.
+          }
+          
+          if (linuxSyncPreset == 'default' || linuxSyncPreset == 'auto') {
             // Check for RetroDeck
             final home = io.Platform.environment['HOME'] ?? '';
             final retrodeckConfig = p.join(home, '.var', 'app', 'net.retrodeck.retrodeck');
             if (await io.Directory(retrodeckConfig).exists()) {
               linuxSyncPreset = 'retrodeck';
               _linuxStrategy = RetroDeckStrategy();
-            } else {
-              linuxSyncPreset = 'default';
-              _linuxStrategy = NativeLinuxStrategy();
+              await _prefs.setString(_linuxSyncPresetKey, 'retrodeck');
             }
           }
         }
@@ -230,6 +239,10 @@ class DirectoryService {
   }
 
   Future<void> setEmudeckRoot(String path) async {
+    // If user picked the 'Emulation' folder itself, go up one level
+    if (p.basename(path).toLowerCase() == 'emulation') {
+      path = p.dirname(path);
+    }
     await _prefs.setString(_emudeckRootPathKey, path);
     emudeckRootPath = path;
     // Re-initialize to update paths based on new root
diff --git a/lib/ui/screens/onboarding_screen.dart b/lib/ui/screens/onboarding_screen.dart
index 37282e8..783158b 100644
--- a/lib/ui/screens/onboarding_screen.dart
+++ b/lib/ui/screens/onboarding_screen.dart
@@ -138,24 +138,25 @@ class _OnboardingScreenState extends ConsumerState<OnboardingScreen> {
     await SecureStorageService.write('rommApiKey', _apiKeyController.text.trim(), prefs);
     
     if (io.Platform.isLinux && _linuxPreset != 'default' && _presetRoot != null) {
+      await prefs.setString('linuxSyncPreset', _linuxPreset);
       if (_linuxPreset == 'emudeck') {
-        _romsRoot = p.join(_presetRoot!, 'roms');
-        _emusRoot = p.join(_presetRoot!, 'tools', 'launchers');
         await prefs.setString('emudeckRootPath', _presetRoot!);
       } else if (_linuxPreset == 'retrodeck') {
-        _romsRoot = p.join(_presetRoot!, 'roms');
-        _emusRoot = p.join(_presetRoot!, 'tools');
         await prefs.setString('retrodeckRootPath', _presetRoot!);
       }
-      await prefs.setString('linuxSyncPreset', _linuxPreset);
+      // Clear custom paths so DirectoryService uses the preset/root logic
+      await prefs.remove('romsRootPath');
+      await prefs.remove('emulatorsRootPath');
     } else if (io.Platform.isLinux) {
       await prefs.setString('linuxSyncPreset', 'default');
+      if (_romsRoot != null) await prefs.setString('romsRootPath', _romsRoot!);
+      if (_emusRoot != null) await prefs.setString('emulatorsRootPath', _emusRoot!);
+    } else {
+      // Non-Linux behavior
+      if (_romsRoot != null) await prefs.setString('romsRootPath', _romsRoot!);
+      if (_emusRoot != null) await prefs.setString('emulatorsRootPath', _emusRoot!);
     }
     
-    // Save Storage Config
-    if (_romsRoot != null) await prefs.setString('romsRootPath', _romsRoot!);
-    if (_emusRoot != null) await prefs.setString('emulatorsRootPath', _emusRoot!);
-    
     // Invalidate providers to trigger reload
     ref.invalidate(rommConfigProvider);
     ref.invalidate(rommServiceProvider);
@@ -413,7 +414,13 @@ class _OnboardingScreenState extends ConsumerState<OnboardingScreen> {
               currentPath: _presetRoot ?? 'Select root directory...',
               onTap: () async {
                 final path = await FilePicker.platform.getDirectoryPath();
-                if (path != null) setState(() => _presetRoot = path);
+                if (path != null) {
+                  var finalPath = path;
+                  if (p.basename(finalPath).toLowerCase() == 'emulation') {
+                    finalPath = p.dirname(finalPath);
+                  }
+                  setState(() => _presetRoot = finalPath);
+                }
               },
             ),
           ] else ...[
diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart
index 5938745..80a86aa 100644
--- a/lib/ui/screens/settings_screen.dart
+++ b/lib/ui/screens/settings_screen.dart
@@ -248,6 +248,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
         const Text('Linux App Layout', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
         const SizedBox(height: 8),
         DropdownButtonFormField<String>(
+          key: ValueKey(preset),
           initialValue: preset,
           decoration: const InputDecoration(border: OutlineInputBorder()),
           items: const [
@@ -265,7 +266,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
         const SizedBox(height: 16),
       ],
 
-      if (io.Platform.isLinux && preset != 'default') ...[
+      if (io.Platform.isLinux && (preset == 'emudeck' || preset == 'retrodeck')) ...[
         _buildPathRow(
           label: '${preset == 'emudeck' ? 'EmuDeck' : 'RetroDeck'} Installation Root',
           currentPath: preset == 'emudeck' 
@@ -277,39 +278,56 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
                 await directoryService.setEmudeckRoot(p);
               } else {
                 await prefs.setString('retrodeckRootPath', p);
-                // Trigger re-init so paths compute
                 await directoryService.initialize();
               }
               ref.invalidate(directoryServiceProvider); 
             } 
           },
         ),
-        const SizedBox(height: 12),
-        // Read-only views of the computed paths
+        const SizedBox(height: 16),
+        const Text('Computed Paths (Read-only)', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey)),
+        const SizedBox(height: 8),
         _buildPathRow(
-          label: 'Computed ROMs Directory',
+          label: 'ROMs Directory',
           currentPath: directoryService.romsRootPath,
           onChanged: null,
         ),
         const SizedBox(height: 12),
         _buildPathRow(
-          label: 'Computed Emulators Directory',
+          label: 'Emulators Directory',
           currentPath: directoryService.emulatorsRootPath,
           onChanged: null,
         ),
       ] else ...[
+        // This handles both non-Linux OSs and Linux 'Manual' mode
         _buildPathRow(
           label: 'ROMs Directory',
           currentPath: directoryService.romsRootPath,
-          onChanged: (p) async { if (p != null) { await directoryService.setRomsRoot(p); ref.invalidate(directoryServiceProvider); } },
-          onReset: () async { await directoryService.resetRomsRoot(); ref.invalidate(directoryServiceProvider); },
+          onChanged: (p) async { 
+            if (p != null) { 
+              await directoryService.setRomsRoot(p); 
+              ref.invalidate(directoryServiceProvider); 
+            } 
+          },
+          onReset: () async { 
+            await directoryService.resetRomsRoot(); 
+            ref.invalidate(directoryServiceProvider); 
+          },
         ),
-        const SizedBox(height: 12),
+        const SizedBox(height: 16),
         _buildPathRow(
           label: 'Emulators Directory',
           currentPath: directoryService.emulatorsRootPath,
-          onChanged: (p) async { if (p != null) { await directoryService.setEmulatorsRoot(p); ref.invalidate(directoryServiceProvider); } },
-          onReset: () async { await directoryService.resetEmulatorsRoot(); ref.invalidate(directoryServiceProvider); },
+          onChanged: (p) async { 
+            if (p != null) { 
+              await directoryService.setEmulatorsRoot(p); 
+              ref.invalidate(directoryServiceProvider); 
+            } 
+          },
+          onReset: () async { 
+            await directoryService.resetEmulatorsRoot(); 
+            ref.invalidate(directoryServiceProvider); 
+          },
         ),
       ],
       const SizedBox(height: 16),

Clone this wiki locally