committed by
crueter
14 changed files with 647 additions and 1 deletions
-
3src/android/app/src/main/AndroidManifest.xml
-
3src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
-
40src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/PathSetting.kt
-
1src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
-
16src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
-
236src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
-
50src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
-
22src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt
-
64src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/PathViewHolder.kt
-
18src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
-
92src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PathUtil.kt
-
39src/android/app/src/main/jni/android_config.cpp
-
36src/android/app/src/main/jni/native_config.cpp
-
28src/android/app/src/main/res/values/strings.xml
@ -0,0 +1,40 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.features.settings.model.view |
|||
|
|||
import androidx.annotation.DrawableRes |
|||
import androidx.annotation.StringRes |
|||
|
|||
class PathSetting( |
|||
@StringRes titleId: Int = 0, |
|||
titleString: String = "", |
|||
@StringRes descriptionId: Int = 0, |
|||
descriptionString: String = "", |
|||
@DrawableRes val iconId: Int = 0, |
|||
val pathType: PathType, |
|||
val defaultPathGetter: () -> String, |
|||
val currentPathGetter: () -> String, |
|||
val pathSetter: (String) -> Unit |
|||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) { |
|||
|
|||
override val type = TYPE_PATH |
|||
|
|||
enum class PathType { |
|||
SAVE_DATA, |
|||
NAND, |
|||
SDMC |
|||
} |
|||
|
|||
fun getCurrentPath(): String = currentPathGetter() |
|||
|
|||
fun getDefaultPath(): String = defaultPathGetter() |
|||
|
|||
fun setPath(path: String) = pathSetter(path) |
|||
|
|||
fun isUsingDefaultPath(): Boolean = getCurrentPath() == getDefaultPath() |
|||
|
|||
companion object { |
|||
const val TYPE_PATH = 13 |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder |
|||
|
|||
import android.view.View |
|||
import androidx.core.content.res.ResourcesCompat |
|||
import org.yuzu.yuzu_emu.R |
|||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
|||
import org.yuzu.yuzu_emu.features.settings.model.view.PathSetting |
|||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
|||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
|||
import org.yuzu.yuzu_emu.utils.PathUtil |
|||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible |
|||
|
|||
class PathViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
|||
SettingViewHolder(binding.root, adapter) { |
|||
|
|||
private lateinit var setting: PathSetting |
|||
|
|||
override fun bind(item: SettingsItem) { |
|||
setting = item as PathSetting |
|||
binding.icon.setVisible(setting.iconId != 0) |
|||
if (setting.iconId != 0) { |
|||
binding.icon.setImageDrawable( |
|||
ResourcesCompat.getDrawable( |
|||
binding.icon.resources, |
|||
setting.iconId, |
|||
binding.icon.context.theme |
|||
) |
|||
) |
|||
} |
|||
|
|||
binding.textSettingName.text = setting.title |
|||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty()) |
|||
binding.textSettingDescription.text = setting.description |
|||
|
|||
val currentPath = setting.getCurrentPath() |
|||
val displayPath = PathUtil.truncatePathForDisplay(currentPath) |
|||
|
|||
binding.textSettingValue.setVisible(true) |
|||
binding.textSettingValue.text = if (setting.isUsingDefaultPath()) { |
|||
binding.root.context.getString(R.string.default_string) |
|||
} else { |
|||
displayPath |
|||
} |
|||
|
|||
binding.buttonClear.setVisible(!setting.isUsingDefaultPath()) |
|||
binding.buttonClear.text = binding.root.context.getString(R.string.reset_to_default) |
|||
binding.buttonClear.setOnClickListener { |
|||
adapter.onPathReset(setting, bindingAdapterPosition) |
|||
} |
|||
|
|||
setStyle(true, binding) |
|||
} |
|||
|
|||
override fun onClick(clicked: View) { |
|||
adapter.onPathClick(setting, bindingAdapterPosition) |
|||
} |
|||
|
|||
override fun onLongClick(clicked: View): Boolean { |
|||
return false |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu.utils |
|||
|
|||
import android.net.Uri |
|||
import android.provider.DocumentsContract |
|||
import java.io.File |
|||
|
|||
object PathUtil { |
|||
|
|||
/** |
|||
* Converts a content:// URI from the Storage Access Framework to a real filesystem path. |
|||
* |
|||
*/ |
|||
fun getPathFromUri(uri: Uri): String? { |
|||
val docId = try { |
|||
DocumentsContract.getTreeDocumentId(uri) |
|||
} catch (_: Exception) { |
|||
return null |
|||
} |
|||
|
|||
if (docId.startsWith("primary:")) { |
|||
val relativePath = docId.removePrefix("primary:") |
|||
return "/storage/emulated/0/$relativePath" |
|||
} |
|||
|
|||
// external SD cards and other volumes) |
|||
val split = docId.split(":") |
|||
if (split.size >= 2) { |
|||
val volumeId = split[0] |
|||
val relativePath = split.getOrElse(1) { "" } |
|||
val possiblePaths = listOf( |
|||
"/storage/$volumeId/$relativePath", |
|||
"/mnt/media_rw/$volumeId/$relativePath" |
|||
) |
|||
for (path in possiblePaths) { |
|||
val file = File(path) |
|||
if (file.exists() && file.isDirectory) { |
|||
return path |
|||
} |
|||
} |
|||
} |
|||
|
|||
return null |
|||
} |
|||
|
|||
/** |
|||
* Validates that a path is a valid, writable directory. |
|||
* Creates the directory if it doesn't exist. |
|||
*/ |
|||
fun validateDirectory(path: String): Boolean { |
|||
val dir = File(path) |
|||
|
|||
if (!dir.exists()) { |
|||
if (!dir.mkdirs()) { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
return dir.isDirectory && dir.canWrite() |
|||
} |
|||
|
|||
/** |
|||
* Copies a directory recursively from source to destination. |
|||
*/ |
|||
fun copyDirectory(source: File, destination: File, overwrite: Boolean = true): Boolean { |
|||
return try { |
|||
source.copyRecursively(destination, overwrite) |
|||
true |
|||
} catch (_: Exception) { |
|||
false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Checks if a directory has any content. |
|||
*/ |
|||
fun hasContent(path: String): Boolean { |
|||
val dir = File(path) |
|||
return dir.exists() && dir.listFiles()?.isNotEmpty() == true |
|||
} |
|||
|
|||
|
|||
fun truncatePathForDisplay(path: String, maxLength: Int = 40): String { |
|||
return if (path.length > maxLength) { |
|||
"...${path.takeLast(maxLength - 3)}" |
|||
} else { |
|||
path |
|||
} |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue