diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt
index 8704af95c..c9597b386 100644
--- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt
+++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt
@@ -463,7 +463,10 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor(
} else items
drivesWorking.clear()
- val drivesStr = c?.getDrives() ?: Container.DEFAULT_DRIVES
+ val drivesStr = c?.let {
+ com.winlator.cmod.runtime.wine.WineUtils.readDrivesFromPrefix(it)
+ .ifEmpty { it.getDrives() }
+ } ?: Container.DEFAULT_DRIVES
for (drive in Container.drivesIterator(drivesStr)) {
drivesWorking.add(
DriveDraft(
@@ -848,6 +851,10 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor(
c.setEmulator64(emulator64)
c.setWinComponents(wincomponents)
c.setDrives(drivesString)
+ // an existing prefix owns the drive list, so write it there too
+ if (java.io.File(c.rootDir, ".wine/dosdevices").isDirectory) {
+ com.winlator.cmod.runtime.wine.WineUtils.applyDrivesToPrefix(c, drivesString)
+ }
c.setFullscreenStretched(state.fullscreenStretched.value)
c.setUseUnixLibs(state.useUnixLibs.value)
c.setInputType(finalInputType)
diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml
index 9919ec9cb..899617cc9 100644
--- a/app/src/main/res/values-b+es+419/strings.xml
+++ b/app/src/main/res/values-b+es+419/strings.xml
@@ -15,6 +15,8 @@
Seleccionar EXE
Seleccionar carpeta
Carpeta actual
+ Ingresar ruta
+ Ruta no encontrada
Selecciona un archivo .exe válido
Selecciona un archivo .ico o .png válido.
Salir
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 7c2bc7ae8..0afcb6890 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -128,6 +128,8 @@
Vælg
Vælg mappe
Nuværende mappe
+ Indtast sti
+ Stien blev ikke fundet
Lagringsrødder
Gennemse lokale mapper direkte
Ingen mapper tilgængelige her
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 303dbfa8e..ffc75f99e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -128,6 +128,8 @@
Auswählen
Ordner auswählen
Aktueller Ordner
+ Pfad eingeben
+ Pfad nicht gefunden
Speicherorte
Lokale Ordner direkt durchsuchen
Hier sind keine Ordner verfügbar
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 5cc622090..40ce3eb87 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -128,6 +128,8 @@
Seleccionar
Seleccionar carpeta
Carpeta actual
+ Introducir ruta
+ Ruta no encontrada
Ubicaciones de almacenamiento
Explorar carpetas locales directamente
No hay carpetas disponibles aquí
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index f62d0a67c..cc948e9f1 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -15,6 +15,8 @@
Valitse EXE
Valitse kansio
Nykyinen kansio
+ Anna polku
+ Polkua ei löytynyt
Valitse kelvollinen .exe-tiedosto
Valitse kelvollinen .ico- tai .png-tiedosto.
Poistu
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index e8865ddbc..cc6348beb 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -128,6 +128,8 @@
Sélectionner
Sélectionner un dossier
Dossier actuel
+ Saisir le chemin
+ Chemin introuvable
Emplacements de stockage
Parcourir directement les dossiers locaux
Aucun dossier disponible ici
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index 63edff4cd..1c318e812 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -15,6 +15,8 @@
EXE चुनें
फ़ोल्डर चुनें
वर्तमान फ़ोल्डर
+ पथ दर्ज करें
+ पथ नहीं मिला
कृपया मान्य .exe फ़ाइल चुनें
कृपया मान्य .ico या .png फ़ाइल चुनें।
बाहर निकलें
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 99e2cf56c..aa3d9ce0b 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -128,6 +128,8 @@
Seleziona
Seleziona cartella
Cartella corrente
+ Inserisci percorso
+ Percorso non trovato
Percorsi di archiviazione
Sfoglia direttamente le cartelle locali
Nessuna cartella disponibile qui
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index f444592f9..f1dede4f0 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -15,6 +15,8 @@
EXEを選択
フォルダを選択
現在のフォルダ
+ パスを入力
+ パスが見つかりません
有効な.exeファイルを選択してください
有効な.icoまたは.pngファイルを選択してください。
終了
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 3d9dd0d1c..65706aba0 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -128,6 +128,8 @@
선택
폴더 선택
현재 폴더
+ 경로 입력
+ 경로를 찾을 수 없음
저장소 루트
로컬 폴더를 바로 탐색
여기에서 사용할 수 있는 폴더가 없습니다
diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml
index 144ab9a8e..e6ee7acf6 100644
--- a/app/src/main/res/values-no/strings.xml
+++ b/app/src/main/res/values-no/strings.xml
@@ -15,6 +15,8 @@
Velg EXE
Velg mappe
Gjeldende mappe
+ Angi sti
+ Fant ikke stien
Velg en gyldig .exe-fil
Velg en gyldig .ico- eller .png-fil.
Avslutt
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 33f7e49df..402e17c95 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -128,6 +128,8 @@
Wybierz
Wybierz folder
Bieżący folder
+ Wprowadź ścieżkę
+ Nie znaleziono ścieżki
Lokalizacje pamięci
Przeglądaj lokalne foldery bezpośrednio
Brak dostępnych folderów w tej lokalizacji
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 144328d65..8dc46b6ae 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -128,6 +128,8 @@
Selecionar
Selecionar pasta
Pasta atual
+ Inserir caminho
+ Caminho não encontrado
Locais de armazenamento
Navegar pelas pastas locais diretamente
Nenhuma pasta disponível aqui
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 36753863d..ed79dea01 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -15,6 +15,8 @@
Selecionar EXE
Selecionar Pasta
Pasta Atual
+ Introduzir caminho
+ Caminho não encontrado
Selecione um ficheiro .exe válido
Selecione um ficheiro .ico ou .png válido.
Sair
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 2577a0474..a4a1f22cc 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -128,6 +128,8 @@
Selectează
Selectează folder
Folder curent
+ Introduceți calea
+ Calea nu a fost găsită
Locații de stocare
Răsfoiește direct folderele locale
Niciun folder disponibil aici
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index d63098f20..b11a7a883 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -15,6 +15,8 @@
Выбрать EXE
Выбрать папку
Текущая папка
+ Введите путь
+ Путь не найден
Выберите корректный файл .exe
Выберите корректный файл .ico или .png.
Выход
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 5a9dfd660..e57383056 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -15,6 +15,8 @@
Välj EXE
Välj mapp
Aktuell mapp
+ Ange sökväg
+ Sökvägen hittades inte
Välj en giltig .exe-fil
Välj en giltig .ico- eller .png-fil.
Avsluta
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index 048f4e759..19e33d9f4 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -15,6 +15,8 @@
เลือก EXE
เลือกโฟลเดอร์
โฟลเดอร์ปัจจุบัน
+ ป้อนพาธ
+ ไม่พบพาธ
โปรดเลือกไฟล์ .exe ที่ถูกต้อง
โปรดเลือกไฟล์ .ico หรือ .png ที่ถูกต้อง
ออก
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index acd1fe4f0..9d3407a01 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -15,6 +15,8 @@
EXE Seç
Klasör Seç
Geçerli Klasör
+ Yol girin
+ Yol bulunamadı
Lütfen geçerli bir .exe dosyası seçin
Lütfen geçerli bir .ico veya .png dosyası seçin.
Çıkış
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index aa2251751..b6dcc341c 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -128,6 +128,8 @@
Вибрати
Вибрати теку
Поточна тека
+ Введіть шлях
+ Шлях не знайдено
Сховища
Переглядати локальні теки безпосередньо
Тут немає доступних тек
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index fb359c30b..59d94be2b 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -128,6 +128,8 @@
选择
选择文件夹
当前文件夹
+ 输入路径
+ 找不到路径
存储根目录
直接浏览本地文件夹
此处没有可用的文件夹
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 6312633aa..6b75c25c0 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -128,6 +128,8 @@
選擇
選擇資料夾
目前資料夾
+ 輸入路徑
+ 找不到路徑
儲存根目錄
直接瀏覽本機資料夾
這裡沒有可用的資料夾
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 364f9c208..aa83bcf14 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -16,6 +16,8 @@
Select EXE
Select Folder
Current Folder
+ Enter Path
+ Path not found
Please select a valid .exe file
Please select a valid .ico or .png file.
Exit
diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java
index b5429ae71..8f0fa1e77 100644
--- a/app/src/main/runtime/display/XServerDisplayActivity.java
+++ b/app/src/main/runtime/display/XServerDisplayActivity.java
@@ -6803,7 +6803,8 @@ private void setupXEnvironment() throws PackageManager.NameNotFoundException {
normalizeSyncEnvVars(envVars);
ArrayList bindingPaths = new ArrayList<>();
- String drives = shortcut != null ? getShortcutSetting("drives", container.getDrives()) : container.getDrives();
+ String drives = WineUtils.readDrivesFromPrefix(container);
+ if (drives.isEmpty()) drives = container.getDrives();
for (String[] drive : Container.drivesIterator(drives)) {
bindingPaths.add(drive[1]);
}
diff --git a/app/src/main/runtime/wine/WineUtils.java b/app/src/main/runtime/wine/WineUtils.java
index a64996e82..994336d9c 100644
--- a/app/src/main/runtime/wine/WineUtils.java
+++ b/app/src/main/runtime/wine/WineUtils.java
@@ -17,6 +17,7 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -60,10 +61,13 @@ public static String hostPathToMappedWinePath(
String bestDriveLetter = null;
String bestDriveRoot = null;
- String drives =
- container != null && container.getDrives() != null
- ? container.getDrives()
- : Container.DEFAULT_DRIVES;
+ String drives = readDrivesFromPrefix(container);
+ if (drives.isEmpty()) {
+ drives =
+ container != null && container.getDrives() != null
+ ? container.getDrives()
+ : Container.DEFAULT_DRIVES;
+ }
for (String[] drive : Container.drivesIterator(drives)) {
if (drive.length < 2) continue;
@@ -375,50 +379,57 @@ private static boolean isSupportedDriveLetter(String letter) {
return letter.length() == 1 && Character.isLetter(letter.charAt(0));
}
- public static void createDosdevicesSymlinks(
- Container container, @Nullable String gameDirectoryPath, boolean exposeSteamGameLink) {
- Log.d(
- "ContainerLaunch",
- "createDosdevicesSymlinks: rootDir="
- + container.getRootDir().getAbsolutePath()
- + " drives="
- + container.getDrives()
- + " gameDir="
- + gameDirectoryPath);
+ // dosdevices is the source of truth for drives; c: and z: stay app-managed
+ public static String readDrivesFromPrefix(@Nullable Container container) {
+ if (container == null) return "";
File dosdevicesDir = new File(container.getRootDir(), ".wine/dosdevices");
- if (!dosdevicesDir.exists()) {
- boolean created = dosdevicesDir.mkdirs();
- Log.d("ContainerLaunch", "createDosdevicesSymlinks: created dosdevices dir=" + created);
- }
- String dosdevicesPath = dosdevicesDir.getPath();
File[] files = dosdevicesDir.listFiles();
- if (files != null) for (File file : files) if (file.getName().matches("[a-z]:")) file.delete();
+ if (files == null) return "";
+
+ List names = new ArrayList<>();
+ for (File file : files) if (file.getName().matches("[a-z]:")) names.add(file.getName());
+ Collections.sort(names);
+
+ StringBuilder drives = new StringBuilder();
+ for (String name : names) {
+ if (name.equals("c:") || name.equals("z:")) continue;
+ String target = FileUtils.readSymlink(new File(dosdevicesDir, name));
+ if (target.isEmpty()) continue;
+
+ String path = target;
+ if (!new File(target).isAbsolute()) {
+ File resolved = new File(dosdevicesDir, target);
+ try {
+ path = resolved.getCanonicalPath();
+ } catch (IOException e) {
+ path = resolved.getAbsolutePath();
+ }
+ }
+ if (path.isEmpty() || path.contains(":")) continue;
+ drives.append(name.substring(0, 1).toUpperCase(Locale.ENGLISH)).append(':').append(path);
+ }
+ return drives.toString();
+ }
- FileUtils.symlink("../drive_c", dosdevicesPath + "/c:");
- FileUtils.symlink(container.getRootDir().getPath() + "/../..", dosdevicesPath + "/z:");
+ public static void applyDrivesToPrefix(@Nullable Container container, @Nullable String drives) {
+ if (container == null) return;
+ File dosdevicesDir = new File(container.getRootDir(), ".wine/dosdevices");
+ if (!dosdevicesDir.exists()) dosdevicesDir.mkdirs();
+ String dosdevicesPath = dosdevicesDir.getPath();
String packageStorageSuffix = "/com.winnative.cmod/storage";
String legacyPackageStorageSuffix = "/com.winlator.cmod/storage";
- Context context = null;
if (container.getManager() != null && container.getManager().getContext() != null) {
- context = container.getManager().getContext();
- String packageName = context.getPackageName();
- packageStorageSuffix = "/" + packageName + "/storage";
+ packageStorageSuffix =
+ "/" + container.getManager().getContext().getPackageName() + "/storage";
}
- if (context != null) {
- String normalizedDrives = normalizePersistentDrives(context, container.getDrives(), false);
- if (normalizedDrives != null
- && !normalizedDrives.isEmpty()
- && !normalizedDrives.equals(container.getDrives())) {
- container.setDrives(normalizedDrives);
- Log.d("WineUtils", "Normalized launch drives in memory to: " + normalizedDrives);
- }
- }
+ LinkedHashSet applied = new LinkedHashSet<>();
+ for (String[] drive : Container.drivesIterator(drives != null ? drives : "")) {
+ if (drive.length < 2 || "A".equalsIgnoreCase(drive[0])) continue;
+ String letter = drive[0].toLowerCase(Locale.ENGLISH);
+ if (letter.equals("c") || letter.equals("z")) continue;
- int driveCount = 0;
- for (String[] drive : container.drivesIterator()) {
- if ("A".equalsIgnoreCase(drive[0])) continue;
String path = resolveReadableDrivePath(drive[1]);
File linkTarget = new File(path);
path = linkTarget.getAbsolutePath();
@@ -428,11 +439,44 @@ public static void createDosdevicesSymlinks(
linkTarget.mkdirs();
FileUtils.chmod(linkTarget, 0771);
}
- FileUtils.symlink(path, dosdevicesPath + "/" + drive[0].toLowerCase(Locale.ENGLISH) + ":");
- Log.d("ContainerLaunch", "createDosdevicesSymlinks: " + drive[0] + ": -> " + path);
- driveCount++;
+ FileUtils.symlink(path, dosdevicesPath + "/" + letter + ":");
+ applied.add(letter + ":");
+ Log.d("ContainerLaunch", "applyDrivesToPrefix: " + letter + ": -> " + path);
}
- Log.d("ContainerLaunch", "createDosdevicesSymlinks: created " + driveCount + " drive symlinks");
+
+ File[] files = dosdevicesDir.listFiles();
+ if (files != null)
+ for (File file : files) {
+ String name = file.getName();
+ if (!name.matches("[a-z]:") || name.equals("c:") || name.equals("z:")) continue;
+ if (!applied.contains(name)) file.delete();
+ }
+ }
+
+ public static void createDosdevicesSymlinks(
+ Container container, @Nullable String gameDirectoryPath, boolean exposeSteamGameLink) {
+ File dosdevicesDir = new File(container.getRootDir(), ".wine/dosdevices");
+ if (!dosdevicesDir.exists()) dosdevicesDir.mkdirs();
+ String dosdevicesPath = dosdevicesDir.getPath();
+
+ FileUtils.symlink("../drive_c", dosdevicesPath + "/c:");
+ FileUtils.symlink(container.getRootDir().getPath() + "/../..", dosdevicesPath + "/z:");
+
+ // the container config only seeds a prefix that has no drives yet
+ String drives = readDrivesFromPrefix(container);
+ Log.d("ContainerLaunch", "createDosdevicesSymlinks: entries=" + Arrays.toString(dosdevicesDir.list()) + " read=" + drives);
+ if (drives.isEmpty()) {
+ drives = container.getDrives();
+ Context context =
+ container.getManager() != null ? container.getManager().getContext() : null;
+ if (context != null) {
+ String normalizedDrives = normalizePersistentDrives(context, drives, false);
+ if (normalizedDrives != null && !normalizedDrives.isEmpty()) drives = normalizedDrives;
+ }
+ Log.d("ContainerLaunch", "createDosdevicesSymlinks: seeding fresh prefix with " + drives);
+ }
+
+ applyDrivesToPrefix(container, drives);
// Only expose Steam's steamapps/common symlink for actual Steam launches.
if (gameDirectoryPath != null && !gameDirectoryPath.isEmpty()) {
diff --git a/app/src/main/shared/android/DirectoryPickerDialog.kt b/app/src/main/shared/android/DirectoryPickerDialog.kt
index 25986ac6f..6e41f99f2 100644
--- a/app/src/main/shared/android/DirectoryPickerDialog.kt
+++ b/app/src/main/shared/android/DirectoryPickerDialog.kt
@@ -75,6 +75,9 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.material3.OutlinedTextField
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.material3.ripple
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -419,6 +422,8 @@ object DirectoryPickerDialog {
val scope = rememberCoroutineScope()
var currentDir by remember(initialDir.absolutePath) { mutableStateOf(initialDir) }
var selectedFile by remember { mutableStateOf(null) }
+ var showPathInput by remember { mutableStateOf(false) }
+ var pathInputText by remember { mutableStateOf("") }
var rootsExpanded by remember { mutableStateOf(false) }
var refreshTick by remember { mutableStateOf(0) }
var clipboard by remember { mutableStateOf(null) }
@@ -731,7 +736,7 @@ object DirectoryPickerDialog {
footerZone = false
}
LaunchedEffect(menuTarget) { if (menuTarget != null) menuRegistry.reset() }
- LaunchedEffect(renameTarget, showNewFolder, deleteTargets, runTarget, transferProgress != null) {
+ LaunchedEffect(renameTarget, showNewFolder, showPathInput, deleteTargets, runTarget, transferProgress != null) {
overlayRegistry.controllerActive = false
overlayRegistry.reset()
}
@@ -753,11 +758,28 @@ object DirectoryPickerDialog {
footerRegistry.controllerActive = false
contentRegistry.controllerActive = true
}
+ val pathNotFound = activityString(R.string.common_ui_path_not_found)
+
+ fun applyTypedPath(typed: String) {
+ val target = File(typed.trim())
+ if (target.isDirectory) {
+ currentDir = target
+ selectedFile = null
+ } else if (target.isFile) {
+ currentDir = target.parentFile ?: currentDir
+ selectedFile = target
+ } else {
+ Toast.makeText(context, pathNotFound, Toast.LENGTH_SHORT).show()
+ }
+ showPathInput = false
+ }
+
val handlers =
remember(window) {
paneNavHandlers(
onDismiss = {
when {
+ showPathInput -> showPathInput = false
renameTarget != null -> renameTarget = null
showNewFolder -> showNewFolder = false
deleteTargets != null -> deleteTargets = null
@@ -775,9 +797,11 @@ object DirectoryPickerDialog {
onStart = {
val overlayOpen =
rootsExpanded || menuTarget != null || renameTarget != null ||
- showNewFolder || deleteTargets != null || runTarget != null ||
- transferProgress != null
- if (!overlayOpen) {
+ showNewFolder || showPathInput || deleteTargets != null ||
+ runTarget != null || transferProgress != null
+ if (showPathInput) {
+ applyTypedPath(pathInputText)
+ } else if (!overlayOpen) {
if (manage) {
if (clipboard != null) pasteInto(currentDir) else onDismiss()
} else {
@@ -791,8 +815,9 @@ object DirectoryPickerDialog {
when {
menuTarget != null -> menuRegistry
rootsExpanded -> rootsRegistry
- renameTarget != null || showNewFolder || deleteTargets != null ||
- runTarget != null || transferProgress != null -> overlayRegistry
+ renameTarget != null || showNewFolder || showPathInput ||
+ deleteTargets != null || runTarget != null ||
+ transferProgress != null -> overlayRegistry
footerZone -> footerRegistry
else -> contentRegistry
}
@@ -898,7 +923,13 @@ object DirectoryPickerDialog {
}
Spacer(Modifier.height(4.dp))
- CurrentPathCard(path = selectedFile?.name ?: currentDir.absolutePath)
+ CurrentPathCard(
+ path = selectedFile?.name ?: currentDir.absolutePath,
+ onClick = {
+ pathInputText = currentDir.absolutePath
+ showPathInput = true
+ },
+ )
Spacer(Modifier.height(8.dp))
androidx.compose.foundation.layout.Box(
@@ -1186,6 +1217,20 @@ object DirectoryPickerDialog {
}
}
}
+
+ if (showPathInput) {
+ CompositionLocalProvider(LocalPaneNav provides overlayRegistry) {
+ TextInputOverlay(
+ modifier = Modifier.matchParentSize(),
+ title = activityString(R.string.common_ui_enter_path),
+ initial = pathInputText,
+ confirmLabel = activityString(R.string.common_ui_ok),
+ onTextChange = { pathInputText = it },
+ onConfirm = { applyTypedPath(it) },
+ onDismiss = { showPathInput = false },
+ )
+ }
+ }
}
}
@@ -1236,15 +1281,23 @@ object DirectoryPickerDialog {
}
@Composable
- private fun CurrentPathCard(path: String) {
+ private fun CurrentPathCard(
+ path: String,
+ onClick: () -> Unit,
+ ) {
Box(
modifier =
Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
+ .paneNavItem(cornerRadius = 12.dp, onActivate = onClick)
.background(BgDark)
.border(1.dp, CardBorder, RoundedCornerShape(12.dp))
- .padding(horizontal = CurrentPathHorizontalPadding, vertical = 4.dp),
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = onClick,
+ ).padding(horizontal = CurrentPathHorizontalPadding, vertical = 4.dp),
contentAlignment = Alignment.CenterStart,
) {
Text(
@@ -2013,8 +2066,15 @@ object DirectoryPickerDialog {
confirmLabel: String,
onConfirm: (String) -> Unit,
onDismiss: () -> Unit,
+ onTextChange: (String) -> Unit = {},
) {
var text by remember { mutableStateOf(initial) }
+ val focusRequester = remember { FocusRequester() }
+ val keyboard = LocalSoftwareKeyboardController.current
+ LaunchedEffect(Unit) {
+ focusRequester.requestFocus()
+ keyboard?.show()
+ }
Box(
modifier =
modifier
@@ -2048,9 +2108,12 @@ object DirectoryPickerDialog {
Spacer(Modifier.height(12.dp))
OutlinedTextField(
value = text,
- onValueChange = { text = it },
+ onValueChange = {
+ text = it
+ onTextChange(it)
+ },
singleLine = true,
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier.fillMaxWidth().focusRequester(focusRequester),
)
Spacer(Modifier.height(16.dp))
Row(