early-access version 3684

This commit is contained in:
pineappleEA 2023-06-16 07:32:46 +02:00
parent 915fe36508
commit cff9ef374b
60 changed files with 551 additions and 287 deletions

View file

@ -1,7 +1,7 @@
yuzu emulator early access yuzu emulator early access
============= =============
This is the source code for early-access 3682. This is the source code for early-access 3684.
## Legal Notice ## Legal Notice

View file

@ -2,7 +2,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import android.annotation.SuppressLint import android.annotation.SuppressLint
import kotlin.collections.setOf
import org.jetbrains.kotlin.konan.properties.Properties import org.jetbrains.kotlin.konan.properties.Properties
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
plugins { plugins {
id("com.android.application") id("com.android.application")
@ -10,6 +12,7 @@ plugins {
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.8.21" kotlin("plugin.serialization") version "1.8.21"
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
} }
/** /**
@ -44,16 +47,6 @@ android {
jniLibs.useLegacyPackaging = true jniLibs.useLegacyPackaging = true
} }
lint {
// This is important as it will run lint but not abort on error
// Lint has some overly obnoxious "errors" that should really be warnings
abortOnError = false
//Uncomment disable lines for test builds...
//disable 'MissingTranslation'bin
//disable 'ExtraTranslation'
}
defaultConfig { defaultConfig {
// TODO If this is ever modified, change application_id in strings.xml // TODO If this is ever modified, change application_id in strings.xml
applicationId = "org.yuzu.yuzu_emu" applicationId = "org.yuzu.yuzu_emu"
@ -167,6 +160,23 @@ android {
} }
} }
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint {
version.set("0.47.0")
android.set(true)
ignoreFailures.set(false)
disabledRules.set(
setOf(
"no-wildcard-imports",
"package-name"
)
)
reporters {
reporter(ReporterType.CHECKSTYLE)
}
}
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.10.1") implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.6.1")

View file

@ -14,16 +14,18 @@ import android.widget.TextView
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
import org.yuzu.yuzu_emu.utils.FileUtil.exists
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
import org.yuzu.yuzu_emu.utils.Log.error import org.yuzu.yuzu_emu.utils.Log.error
import org.yuzu.yuzu_emu.utils.Log.verbose import org.yuzu.yuzu_emu.utils.Log.verbose
import org.yuzu.yuzu_emu.utils.Log.warning import org.yuzu.yuzu_emu.utils.Log.warning
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
import java.lang.ref.WeakReference
/** /**
* Class which contains methods that interact * Class which contains methods that interact
@ -74,7 +76,9 @@ object NativeLibrary {
fun openContentUri(path: String?, openmode: String?): Int { fun openContentUri(path: String?, openmode: String?): Int {
return if (isNativePath(path!!)) { return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode) YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else openContentUri(appContext, path, openmode) } else {
openContentUri(appContext, path, openmode)
}
} }
@Keep @Keep
@ -82,7 +86,29 @@ object NativeLibrary {
fun getSize(path: String?): Long { fun getSize(path: String?): Long {
return if (isNativePath(path!!)) { return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path) YuzuApplication.documentsTree!!.getFileSize(path)
} else getFileSize(appContext, path) } else {
getFileSize(appContext, path)
}
}
@Keep
@JvmStatic
fun exists(path: String?): Boolean {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path)
} else {
exists(appContext, path)
}
}
@Keep
@JvmStatic
fun isDirectory(path: String?): Boolean {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path)
} else {
isDirectory(appContext, path)
}
} }
/** /**
@ -436,7 +462,9 @@ object NativeLibrary {
Html.FROM_HTML_MODE_LEGACY Html.FROM_HTML_MODE_LEGACY
) )
) )
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() } .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
emulationActivity.finish()
}
.setOnDismissListener { emulationActivity.finish() } .setOnDismissListener { emulationActivity.finish() }
emulationActivity.runOnUiThread { emulationActivity.runOnUiThread {
val alert = builder.create() val alert = builder.create()

View file

@ -7,12 +7,12 @@ import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import java.io.File
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DocumentsTree import org.yuzu.yuzu_emu.utils.DocumentsTree
import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import java.io.File
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
class YuzuApplication : Application() { class YuzuApplication : Application() {
private fun createNotificationChannels() { private fun createNotificationChannels() {
@ -21,7 +21,9 @@ class YuzuApplication : Application() {
getString(R.string.emulation_notification_channel_name), getString(R.string.emulation_notification_channel_name),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
) )
emulationChannel.description = getString(R.string.emulation_notification_channel_description) emulationChannel.description = getString(
R.string.emulation_notification_channel_description
)
emulationChannel.setSound(null, null) emulationChannel.setSound(null, null)
emulationChannel.vibrationPattern = null emulationChannel.vibrationPattern = null
@ -48,7 +50,7 @@ class YuzuApplication : Application() {
GpuDriverHelper.initializeDriverParameters(applicationContext) GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo() NativeLibrary.logDeviceInfo()
createNotificationChannels(); createNotificationChannels()
} }
companion object { companion object {

View file

@ -33,6 +33,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
@ -45,7 +46,6 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.NfcReader import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener { class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
@ -256,7 +256,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} }
} }
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): PictureInPictureParams.Builder { private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
PictureInPictureParams.Builder {
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
0 -> Rational(16, 9) 0 -> Rational(16, 9)
1 -> Rational(4, 3) 1 -> Rational(4, 3)
@ -267,7 +268,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return this.apply { aspectRatio?.let { setAspectRatio(it) } } return this.apply { aspectRatio?.let { setAspectRatio(it) } }
} }
private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): PictureInPictureParams.Builder { private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder():
PictureInPictureParams.Builder {
val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf()
val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
@ -310,7 +312,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
pictureInPictureParamsBuilder.setAutoEnterEnabled(BooleanSetting.PICTURE_IN_PICTURE.boolean) pictureInPictureParamsBuilder.setAutoEnterEnabled(
BooleanSetting.PICTURE_IN_PICTURE.boolean
)
} }
setPictureInPictureParams(pictureInPictureParamsBuilder.build()) setPictureInPictureParams(pictureInPictureParamsBuilder.build())
} }
@ -341,7 +345,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
} else { } else {
try { try {
unregisterReceiver(pictureInPictureReceiver) unregisterReceiver(pictureInPictureReceiver)
} catch (ignored : Exception) { } catch (ignored: Exception) {
} }
} }
} }

View file

@ -28,10 +28,9 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
class GameAdapter(private val activity: AppCompatActivity) : class GameAdapter(private val activity: AppCompatActivity) :
@ -60,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) :
override fun onClick(view: View) { override fun onClick(view: View) {
val holder = view.tag as GameViewHolder val holder = view.tag as GameViewHolder
val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true val gameExists = DocumentFile.fromSingleUri(
YuzuApplication.appContext,
Uri.parse(holder.game.path)
)?.exists() == true
if (!gameExists) { if (!gameExists) {
Toast.makeText( Toast.makeText(
YuzuApplication.appContext, YuzuApplication.appContext,

View file

@ -58,7 +58,8 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
) )
when (option.titleId) { when (option.titleId) {
R.string.get_early_access -> binding.optionLayout.background = R.string.get_early_access ->
binding.optionLayout.background =
ContextCompat.getDrawable( ContextCompat.getDrawable(
binding.optionCard.context, binding.optionCard.context,
R.drawable.premium_background R.drawable.premium_background

View file

@ -12,10 +12,10 @@ import android.view.WindowInsets
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import java.io.Serializable
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
import java.io.Serializable
@Keep @Keep
object SoftwareKeyboard { object SoftwareKeyboard {
@ -40,7 +40,8 @@ object SoftwareKeyboard {
// There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result.
val handler = Handler(Looper.myLooper()!!) val handler = Handler(Looper.myLooper()!!)
val delayMs = 500 val delayMs = 500
handler.postDelayed(object : Runnable { handler.postDelayed(
object : Runnable {
override fun run() { override fun run() {
val insets = ViewCompat.getRootWindowInsets(overlayView) val insets = ViewCompat.getRootWindowInsets(overlayView)
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
@ -52,7 +53,9 @@ object SoftwareKeyboard {
// No longer visible, submit the result. // No longer visible, submit the result.
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
} }
}, delayMs.toLong()) },
delayMs.toLong()
)
} }
@JvmStatic @JvmStatic

View file

@ -20,7 +20,10 @@ object DiskShaderCacheProgress {
emulationActivity.getString(R.string.loading), emulationActivity.getString(R.string.loading),
emulationActivity.getString(R.string.preparing_shaders) emulationActivity.getString(R.string.preparing_shaders)
) )
fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) fragment.show(
emulationActivity.supportFragmentManager,
ShaderProgressDialogFragment.TAG
)
} }
synchronized(finishLock) { finishLock.wait() } synchronized(finishLock) { finishLock.wait() }
} }

View file

@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() {
shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
alertDialog.setMessage(msg) alertDialog.setMessage(msg)
} }
synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } synchronized(DiskShaderCacheProgress.finishLock) {
DiskShaderCacheProgress.finishLock.notifyAll()
}
} }
override fun onDestroyView() { override fun onDestroyView() {

View file

@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.DocumentsProvider import android.provider.DocumentsProvider
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import java.io.*
import org.yuzu.yuzu_emu.BuildConfig import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.getPublicFilesDir import org.yuzu.yuzu_emu.getPublicFilesDir
import java.io.*
class DocumentProvider : DocumentsProvider() { class DocumentProvider : DocumentsProvider() {
private val baseDirectory: File private val baseDirectory: File
@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() {
DocumentsContract.Document.COLUMN_SIZE DocumentsContract.Document.COLUMN_SIZE
) )
const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user" const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user"
const val ROOT_ID: String = "root" const val ROOT_ID: String = "root"
} }
@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() {
private fun getFile(documentId: String): File { private fun getFile(documentId: String): File {
if (documentId.startsWith(ROOT_ID)) { if (documentId.startsWith(ROOT_ID)) {
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found") if (!file.exists()) {
throw FileNotFoundException(
"${file.absolutePath} ($documentId) not found"
)
}
return file return file
} else { } else {
throw FileNotFoundException("'$documentId' is not in any known root") throw FileNotFoundException("'$documentId' is not in any known root")
@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() {
add(DocumentsContract.Root.COLUMN_SUMMARY, null) add(DocumentsContract.Root.COLUMN_SUMMARY, null)
add( add(
DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.COLUMN_FLAGS,
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD DocumentsContract.Root.FLAG_SUPPORTS_CREATE or
DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
) )
add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name))
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
@ -127,12 +132,14 @@ class DocumentProvider : DocumentsProvider() {
try { try {
if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
if (!newFile.mkdir()) if (!newFile.mkdir()) {
throw IOException("Failed to create directory") throw IOException("Failed to create directory")
}
} else { } else {
if (!newFile.createNewFile()) if (!newFile.createNewFile()) {
throw IOException("Failed to create file") throw IOException("Failed to create file")
} }
}
} catch (e: IOException) { } catch (e: IOException) {
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
} }
@ -142,47 +149,65 @@ class DocumentProvider : DocumentsProvider() {
override fun deleteDocument(documentId: String?) { override fun deleteDocument(documentId: String?) {
val file = getFile(documentId!!) val file = getFile(documentId!!)
if (!file.delete()) if (!file.delete()) {
throw FileNotFoundException("Couldn't delete document with ID '$documentId'") throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
} }
}
override fun removeDocument(documentId: String, parentDocumentId: String?) { override fun removeDocument(documentId: String, parentDocumentId: String?) {
val parent = getFile(parentDocumentId!!) val parent = getFile(parentDocumentId!!)
val file = getFile(documentId) val file = getFile(documentId)
if (parent == file || file.parentFile == null || file.parentFile!! == parent) { if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
if (!file.delete()) if (!file.delete()) {
throw FileNotFoundException("Couldn't delete document with ID '$documentId'") throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
}
} else { } else {
throw FileNotFoundException("Couldn't delete document with ID '$documentId'") throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
} }
} }
override fun renameDocument(documentId: String?, displayName: String?): String { override fun renameDocument(documentId: String?, displayName: String?): String {
if (displayName == null) if (displayName == null) {
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null") throw FileNotFoundException(
"Couldn't rename document '$documentId' as the new name is null"
)
}
val sourceFile = getFile(documentId!!) val sourceFile = getFile(documentId!!)
val sourceParentFile = sourceFile.parentFile val sourceParentFile = sourceFile.parentFile
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent") ?: throw FileNotFoundException(
"Couldn't rename document '$documentId' as it has no parent"
)
val destFile = sourceParentFile.resolve(displayName) val destFile = sourceParentFile.resolve(displayName)
try { try {
if (!sourceFile.renameTo(destFile)) if (!sourceFile.renameTo(destFile)) {
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'") throw FileNotFoundException(
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'"
)
}
} catch (e: Exception) { } catch (e: Exception) {
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}") throw FileNotFoundException(
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " +
"${e.message}"
)
} }
return getDocumentId(destFile) return getDocumentId(destFile)
} }
private fun copyDocument( private fun copyDocument(
sourceDocumentId: String, sourceParentDocumentId: String, sourceDocumentId: String,
sourceParentDocumentId: String,
targetParentDocumentId: String? targetParentDocumentId: String?
): String { ): String {
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) {
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") throw FileNotFoundException(
"Couldn't copy document '$sourceDocumentId' as its parent is not " +
"'$sourceParentDocumentId'"
)
}
return copyDocument(sourceDocumentId, targetParentDocumentId) return copyDocument(sourceDocumentId, targetParentDocumentId)
} }
@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() {
val newFile = parent.resolveWithoutConflict(oldFile.name) val newFile = parent.resolveWithoutConflict(oldFile.name)
try { try {
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true))) if (!(
newFile.createNewFile() && newFile.setWritable(true) &&
newFile.setReadable(true)
)
) {
throw IOException("Couldn't create new file") throw IOException("Couldn't create new file")
}
FileInputStream(oldFile).use { inStream -> FileInputStream(oldFile).use { inStream ->
FileOutputStream(newFile).use { outStream -> FileOutputStream(newFile).use { outStream ->
@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() {
} }
override fun moveDocument( override fun moveDocument(
sourceDocumentId: String, sourceParentDocumentId: String?, sourceDocumentId: String,
sourceParentDocumentId: String?,
targetParentDocumentId: String? targetParentDocumentId: String?
): String { ): String {
try { try {
val newDocumentId = copyDocument( val newDocumentId = copyDocument(
sourceDocumentId, sourceParentDocumentId!!, sourceDocumentId,
sourceParentDocumentId!!,
targetParentDocumentId targetParentDocumentId
) )
removeDocument(sourceDocumentId, sourceParentDocumentId) removeDocument(sourceDocumentId, sourceParentDocumentId)
@ -245,34 +277,41 @@ class DocumentProvider : DocumentsProvider() {
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
add( add(
DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_DISPLAY_NAME,
if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name if (localFile == baseDirectory) {
context!!.getString(R.string.app_name)
} else {
localFile.name
}
) )
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
add(DocumentsContract.Document.COLUMN_FLAGS, flags) add(DocumentsContract.Document.COLUMN_FLAGS, flags)
if (localFile == baseDirectory) if (localFile == baseDirectory) {
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
} }
}
return cursor return cursor
} }
private fun getTypeForFile(file: File): Any { private fun getTypeForFile(file: File): Any {
return if (file.isDirectory) return if (file.isDirectory) {
DocumentsContract.Document.MIME_TYPE_DIR DocumentsContract.Document.MIME_TYPE_DIR
else } else {
getTypeForName(file.name) getTypeForName(file.name)
} }
}
private fun getTypeForName(name: String): Any { private fun getTypeForName(name: String): Any {
val lastDot = name.lastIndexOf('.') val lastDot = name.lastIndexOf('.')
if (lastDot >= 0) { if (lastDot >= 0) {
val extension = name.substring(lastDot + 1) val extension = name.substring(lastDot + 1)
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
if (mime != null) if (mime != null) {
return mime return mime
} }
}
return "application/octect-stream" return "application/octect-stream"
} }

View file

@ -4,11 +4,11 @@
package org.yuzu.yuzu_emu.features.settings.model package org.yuzu.yuzu_emu.features.settings.model
import android.text.TextUtils import android.text.TextUtils
import java.util.*
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import java.util.*
class Settings { class Settings {
private var gameId: String? = null private var gameId: String? = null

View file

@ -4,7 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
class SingleChoiceSetting( class SingleChoiceSetting(
setting: AbstractIntSetting?, setting: AbstractIntSetting?,

View file

@ -3,13 +3,11 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import kotlin.math.roundToInt
class SliderSetting( class SliderSetting(
setting: AbstractSetting?, setting: AbstractSetting?,
@ -19,7 +17,7 @@ class SliderSetting(
val max: Int, val max: Int,
val units: String, val units: String,
val key: String? = null, val key: String? = null,
val defaultValue: Int? = null, val defaultValue: Int? = null
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER override val type = TYPE_SLIDER

View file

@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
class StringSingleChoiceSetting( class StringSingleChoiceSetting(
val key: String? = null, val key: String? = null,
@ -22,7 +21,9 @@ class StringSingleChoiceSetting(
if (valuesId == null) return null if (valuesId == null) return null
return if (index >= 0 && index < valuesId.size) { return if (index >= 0 && index < valuesId.size) {
valuesId[index] valuesId[index]
} else "" } else {
""
}
} }
val selectedValue: String val selectedValue: String

View file

@ -3,8 +3,6 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
class SubmenuSetting( class SubmenuSetting(
titleId: Int, titleId: Int,
descriptionId: Int, descriptionId: Int,

View file

@ -8,18 +8,18 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import org.yuzu.yuzu_emu.NativeLibrary import java.io.IOException
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
@ -30,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
import java.io.IOException
class SettingsActivity : AppCompatActivity(), SettingsActivityView { class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private val presenter = SettingsActivityPresenter(this) private val presenter = SettingsActivityPresenter(this)
@ -60,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
setSupportActionBar(binding.toolbarSettings) setSupportActionBar(binding.toolbarSettings)
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
binding.navigationBarShade.setBackgroundColor( binding.navigationBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity( ThemeHelper.getColorWithOpacity(
MaterialColors.getColor( MaterialColors.getColor(
@ -76,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
this, this,
object : OnBackPressedCallback(true) { object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() = navigateBack() override fun handleOnBackPressed() = navigateBack()
}) }
)
setInsets() setInsets()
} }
@ -149,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private fun areSystemAnimationsEnabled(): Boolean { private fun areSystemAnimationsEnabled(): Boolean {
val duration = android.provider.Settings.Global.getFloat( val duration = android.provider.Settings.Global.getFloat(
contentResolver, contentResolver,
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
1f
) )
val transition = android.provider.Settings.Global.getFloat( val transition = android.provider.Settings.Global.getFloat(
contentResolver, contentResolver,
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
1f
) )
return duration != 0f && transition != 0f return duration != 0f && transition != 0f
} }
@ -208,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
private fun setInsets() { private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.frameContent
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.updatePadding( view.updatePadding(

View file

@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import java.io.File
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import java.io.File
class SettingsActivityPresenter(private val activityView: SettingsActivityView) { class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
val settings: Settings get() = activityView.settings val settings: Settings get() = activityView.settings
@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
private fun prepareDirectoriesIfNeeded() { private fun prepareDirectoriesIfNeeded() {
val configFile = val configFile =
File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") File(
"${DirectoryInitialization.userDirectory}/config/" +
"${SettingsFile.FILE_NAME_CONFIG}.ini"
)
if (!configFile.exists()) { if (!configFile.exists()) {
Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") Log.error(
"${DirectoryInitialization.userDirectory}/config/" +
"${SettingsFile.FILE_NAME_CONFIG}.ini"
)
Log.error("yuzu config file could not be found!") Log.error("yuzu config file could not be found!")
} }

View file

@ -13,7 +13,6 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.setFragmentResultListener
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder

View file

@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
settingsAdapter = SettingsAdapter(this, requireActivity()) settingsAdapter = SettingsAdapter(this, requireActivity())
val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL) val dividerDecoration = MaterialDividerItemDecoration(
requireContext(),
LinearLayoutManager.VERTICAL
)
dividerDecoration.isLastItemDecorated = false dividerDecoration.isLastItemDecorated = false
binding.listSettings.apply { binding.listSettings.apply {
adapter = settingsAdapter adapter = settingsAdapter
@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
} }
private fun setInsets() { private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.listSettings
) { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(bottom = insets.bottom) view.updatePadding(bottom = insets.bottom)
windowInsets windowInsets

View file

@ -7,7 +7,6 @@ import android.content.SharedPreferences
import android.os.Build import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
@ -236,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
sl.apply { sl.apply {
add( add(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.RENDERER_ACCURACY, IntSetting.RENDERER_ACCURACY,

View file

@ -4,15 +4,15 @@
package org.yuzu.yuzu_emu.features.settings.ui.viewholder package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View import android.view.View
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle import java.time.format.FormatStyle
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) { SettingViewHolder(binding.root, adapter) {

View file

@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View import android.view.View
import android.widget.CompoundButton import android.widget.CompoundButton
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :

View file

@ -3,6 +3,8 @@
package org.yuzu.yuzu_emu.features.settings.utils package org.yuzu.yuzu_emu.features.settings.utils
import java.io.*
import java.util.*
import org.ini4j.Wini import org.ini4j.Wini
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
import org.yuzu.yuzu_emu.utils.BiMap import org.yuzu.yuzu_emu.utils.BiMap
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import java.io.*
import java.util.*
/** /**
* Contains static methods for interacting with .ini files in which settings are stored. * Contains static methods for interacting with .ini files in which settings are stored.
@ -137,9 +137,12 @@ object SettingsFile {
for (settingKey in sortedKeySet) { for (settingKey in sortedKeySet) {
val setting = settings[settingKey] val setting = settings[settingKey]
NativeLibrary.setUserSetting( NativeLibrary.setUserSetting(
gameId, mapSectionNameFromIni( gameId,
mapSectionNameFromIni(
section.name section.name
), setting!!.key, setting.valueAsString ),
setting!!.key,
setting.valueAsString
) )
} }
} }
@ -148,13 +151,17 @@ object SettingsFile {
private fun mapSectionNameFromIni(generalSectionName: String): String? { private fun mapSectionNameFromIni(generalSectionName: String): String? {
return if (sectionsMap.getForward(generalSectionName) != null) { return if (sectionsMap.getForward(generalSectionName) != null) {
sectionsMap.getForward(generalSectionName) sectionsMap.getForward(generalSectionName)
} else generalSectionName } else {
generalSectionName
}
} }
private fun mapSectionNameToIni(generalSectionName: String): String { private fun mapSectionNameToIni(generalSectionName: String): String {
return if (sectionsMap.getBackward(generalSectionName) != null) { return if (sectionsMap.getBackward(generalSectionName) != null) {
sectionsMap.getBackward(generalSectionName).toString() sectionsMap.getBackward(generalSectionName).toString()
} else generalSectionName } else {
generalSectionName
}
} }
fun getSettingsFile(fileName: String): File { fun getSettingsFile(fileName: String): File {

View file

@ -66,7 +66,11 @@ class AboutFragment : Fragment() {
true true
} }
binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) } binding.buttonContributors.setOnClickListener {
openLink(
getString(R.string.contributors_link)
)
}
binding.buttonLicenses.setOnClickListener { binding.buttonLicenses.setOnClickListener {
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
@ -101,7 +105,9 @@ class AboutFragment : Fragment() {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())

View file

@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() {
parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
} }
binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) } binding.getEarlyAccessButton.setOnClickListener {
openLink(
getString(R.string.play_store_link)
)
}
setInsets() setInsets()
} }
@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())

View file

@ -83,7 +83,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
onReturnFromSettings = context.activityResultRegistry.register( onReturnFromSettings = context.activityResultRegistry.register(
"SettingsResult", ActivityResultContracts.StartActivityForResult() "SettingsResult",
ActivityResultContracts.StartActivityForResult()
) { ) {
binding.surfaceEmulation.setAspectRatio( binding.surfaceEmulation.setAspectRatio(
when (IntSetting.RENDERER_ASPECT_RATIO.int) { when (IntSetting.RENDERER_ASPECT_RATIO.int) {
@ -191,9 +192,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
requireActivity(), requireActivity(),
object : OnBackPressedCallback(true) { object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
} else {
binding.drawerLayout.open()
} }
}) }
}
)
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
@ -312,8 +318,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private fun updateScreenLayout() { private fun updateScreenLayout() {
emulationActivity?.let { emulationActivity?.let {
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
Settings.LayoutOption_MobileLandscape -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE Settings.LayoutOption_MobileLandscape ->
Settings.LayoutOption_MobilePortrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
Settings.LayoutOption_MobilePortrait ->
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} }
@ -321,10 +329,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
onConfigurationChanged(resources.configuration) onConfigurationChanged(resources.configuration)
} }
private fun updateFoldableLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { private fun updateFoldableLayout(
val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { emulationActivity: EmulationActivity,
newLayoutInfo: WindowLayoutInfo
) {
val isFolding =
(newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
if (it.isSeparating) { if (it.isSeparating) {
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED emulationActivity.requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
// Restrict emulation and overlays to the top of the screen // Restrict emulation and overlays to the top of the screen
binding.emulationContainer.layoutParams.height = it.bounds.top binding.emulationContainer.layoutParams.height = it.bounds.top
@ -516,18 +529,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
inputScaleSlider.apply { inputScaleSlider.apply {
valueTo = 150F valueTo = 150F
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
addOnChangeListener(Slider.OnChangeListener { _, value, _ -> addOnChangeListener(
Slider.OnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%" inputScaleValue.text = "${value.toInt()}%"
setControlScale(value.toInt()) setControlScale(value.toInt())
}) }
)
} }
inputOpacitySlider.apply { inputOpacitySlider.apply {
valueTo = 100F valueTo = 100F
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
addOnChangeListener(Slider.OnChangeListener { _, value, _ -> addOnChangeListener(
Slider.OnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%" inputOpacityValue.text = "${value.toInt()}%"
setControlOpacity(value.toInt()) setControlOpacity(value.toInt())
}) }
)
} }
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
@ -559,7 +576,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
private fun setInsets() { private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.inGameMenu
) { v: View, windowInsets: WindowInsetsCompat ->
val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
var left = 0 var left = 0
var right = 0 var right = 0
@ -679,8 +698,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
state = State.PAUSED state = State.PAUSED
} }
State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.") State.PAUSED -> Log.warning(
else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.") "[EmulationFragment] Surface cleared while emulation paused."
)
else -> Log.warning(
"[EmulationFragment] Surface cleared while emulation stopped."
)
} }
} }
} }

View file

@ -103,7 +103,9 @@ class HomeSettingsFragment : Fragment() {
R.string.select_games_folder, R.string.select_games_folder,
R.string.select_games_folder_description, R.string.select_games_folder_description,
R.drawable.ic_add R.drawable.ic_add
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, ) {
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
},
HomeSetting( HomeSetting(
R.string.manage_save_data, R.string.manage_save_data,
R.string.import_export_saves_description, R.string.import_export_saves_description,
@ -225,7 +227,11 @@ class HomeSettingsFragment : Fragment() {
val intent = Intent(action) val intent = Intent(action)
intent.addCategory(Intent.CATEGORY_DEFAULT) intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) intent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
return intent return intent
} }
@ -307,7 +313,9 @@ class HomeSettingsFragment : Fragment() {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)

View file

@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.FilenameFilter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.getPublicFilesDir import org.yuzu.yuzu_emu.getPublicFilesDir
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.FilenameFilter
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
class ImportExportSavesFragment : DialogFragment() { class ImportExportSavesFragment : DialogFragment() {
private val context = YuzuApplication.appContext private val context = YuzuApplication.appContext
@ -106,14 +106,16 @@ class ImportExportSavesFragment : DialogFragment() {
saveFolder.walkTopDown().forEach { file -> saveFolder.walkTopDown().forEach { file ->
val zipFileName = val zipFileName =
file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
if (zipFileName == "") if (zipFileName == "") {
return@forEach return@forEach
}
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
zos.putNextEntry(entry) zos.putNextEntry(entry)
if (file.isFile) if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) } file.inputStream().use { fis -> fis.copyTo(zos) }
} }
} }
}
lastZipCreated = outputZipFile lastZipCreated = outputZipFile
} catch (e: Exception) { } catch (e: Exception) {
return false return false
@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
val file = DocumentFile.fromSingleUri( val file = DocumentFile.fromSingleUri(
context, DocumentsContract.buildDocumentUri( context,
DocumentsContract.buildDocumentUri(
DocumentProvider.AUTHORITY, DocumentProvider.AUTHORITY,
"${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
) )

View file

@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel import org.yuzu.yuzu_emu.model.TaskViewModel
class IndeterminateProgressDialogFragment : DialogFragment() { class IndeterminateProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels() private val taskViewModel: TaskViewModel by activityViewModels()

View file

@ -113,7 +113,9 @@ class LicensesFragment : Fragment() {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())

View file

@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler import info.debatty.java.stringsimilarity.JaroWinkler
import java.util.Locale
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.GameAdapter import org.yuzu.yuzu_emu.adapters.GameAdapter
@ -29,8 +30,6 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import java.util.Locale
class SearchFragment : Fragment() { class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null private var _binding: FragmentSearchBinding? = null
@ -130,15 +129,15 @@ class SearchFragment : Fragment() {
R.id.chip_homebrew -> baseList.filter { it.isHomebrew } R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
R.id.chip_retail -> baseList.filter { R.id.chip_retail -> baseList.filter {
FileUtil.hasExtension(it.path, "xci") FileUtil.hasExtension(it.path, "xci") ||
|| FileUtil.hasExtension(it.path, "nsp") FileUtil.hasExtension(it.path, "nsp")
} }
else -> baseList else -> baseList
} }
if (binding.searchText.text.toString().isEmpty() if (binding.searchText.text.toString().isEmpty() &&
&& binding.chipGroup.checkedChipId != View.NO_ID binding.chipGroup.checkedChipId != View.NO_ID
) { ) {
gamesViewModel.setSearchedGames(filteredList) gamesViewModel.setSearchedGames(filteredList)
return return
@ -173,14 +172,16 @@ class SearchFragment : Fragment() {
private fun focusSearch() { private fun focusSearch() {
if (_binding != null) { if (_binding != null) {
binding.searchText.requestFocus() binding.searchText.requestFocus()
val imm = val imm = requireActivity()
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
} }
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)

View file

@ -25,6 +25,7 @@ import androidx.navigation.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.transition.MaterialFadeThrough import com.google.android.material.transition.MaterialFadeThrough
import java.io.File
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.SetupAdapter import org.yuzu.yuzu_emu.adapters.SetupAdapter
@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import java.io.File
class SetupFragment : Fragment() { class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null private var _binding: FragmentSetupBinding? = null
@ -82,7 +82,8 @@ class SetupFragment : Fragment() {
requireActivity().finish() requireActivity().finish()
} }
} }
}) }
)
requireActivity().window.navigationBarColor = requireActivity().window.navigationBarColor =
ContextCompat.getColor(requireContext(), android.R.color.transparent) ContextCompat.getColor(requireContext(), android.R.color.transparent)
@ -148,14 +149,20 @@ class SetupFragment : Fragment() {
R.drawable.ic_add, R.drawable.ic_add,
true, true,
R.string.add_games, R.string.add_games,
{ mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, {
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
},
true, true,
R.string.add_games_warning, R.string.add_games_warning,
R.string.add_games_warning_description, R.string.add_games_warning_description,
R.string.add_games_warning_help, R.string.add_games_warning_help,
{ {
val preferences = val preferences =
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
} }
) )
@ -260,7 +267,9 @@ class SetupFragment : Fragment() {
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private val permissionLauncher = private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { if (!it &&
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
) {
PermissionDeniedDialogFragment().show( PermissionDeniedDialogFragment().show(
childFragmentManager, childFragmentManager,
PermissionDeniedDialogFragment.TAG PermissionDeniedDialogFragment.TAG
@ -315,7 +324,9 @@ class SetupFragment : Fragment() {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.setPadding( view.setPadding(

View file

@ -44,7 +44,9 @@ class AutofitGridLayoutManager(
override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
val width = width val width = width
val height = height val height = height
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) { if (columnWidth > 0 && width > 0 && height > 0 &&
(isColumnWidthChanged || lastWidth != width || lastHeight != height)
) {
val totalSpace: Int = if (orientation == VERTICAL) { val totalSpace: Int = if (orientation == VERTICAL) {
width - paddingRight - paddingLeft width - paddingRight - paddingLeft
} else { } else {

View file

@ -4,9 +4,9 @@
package org.yuzu.yuzu_emu.model package org.yuzu.yuzu_emu.model
import android.os.Parcelable import android.os.Parcelable
import java.util.HashSet
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.HashSet
@Parcelize @Parcelize
@Serializable @Serializable
@ -23,8 +23,9 @@ class Game(
val keyLastPlayedTime get() = "${gameId}_LastPlayed" val keyLastPlayedTime get() = "${gameId}_LastPlayed"
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is Game) if (other !is Game) {
return false return false
}
return hashCode() == other.hashCode() return hashCode() == other.hashCode()
} }

View file

@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.Locale
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import java.util.Locale
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() { class GamesViewModel : ViewModel() {
@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() {
} }
fun reloadGames(directoryChanged: Boolean) { fun reloadGames(directoryChanged: Boolean) {
if (isReloading.value == true) if (isReloading.value == true) {
return return
}
_isReloading.postValue(true) _isReloading.postValue(true)
viewModelScope.launch { viewModelScope.launch {

View file

@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Point import android.graphics.Point
@ -24,6 +23,8 @@ import android.view.WindowInsets
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.window.layout.WindowMetricsCalculator import androidx.window.layout.WindowMetricsCalculator
import kotlin.math.max
import kotlin.math.min
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
import org.yuzu.yuzu_emu.NativeLibrary.StickType import org.yuzu.yuzu_emu.NativeLibrary.StickType
@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import kotlin.math.max
import kotlin.math.min
/** /**
* Draws the interactive input overlay on top of the * Draws the interactive input overlay on top of the
* [SurfaceView] that is rendering emulation. * [SurfaceView] that is rendering emulation.
*/ */
class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), class InputOverlay(context: Context, attrs: AttributeSet?) :
SurfaceView(context, attrs),
OnTouchListener { OnTouchListener {
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
@ -95,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
var shouldUpdateView = false var shouldUpdateView = false
val playerIndex = val playerIndex =
if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device if (NativeLibrary.isHandheldOnly()) {
NativeLibrary.ConsoleDevice
} else {
NativeLibrary.Player1Device
}
for (button in overlayButtons) { for (button in overlayButtons) {
if (!button.updateStatus(event)) { if (!button.updateStatus(event)) {
@ -158,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
shouldUpdateView = true shouldUpdateView = true
} }
if (shouldUpdateView) if (shouldUpdateView) {
invalidate() invalidate()
}
if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
return true return true
@ -668,7 +673,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
R.integer.SWITCH_STICK_L_Y_FOLDABLE R.integer.SWITCH_STICK_L_Y_FOLDABLE
) )
private fun getResourceValue(orientation: String, position: Int) : Float { private fun getResourceValue(orientation: String, position: Int): Float {
return when (orientation) { return when (orientation) {
PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
@ -872,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
if (insets != null) { if (insets != null) {
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) if (insets.boundingRectTop.bottom != 0 &&
insets.boundingRectTop.bottom > maxY / 2
) {
maxY = insets.boundingRectTop.bottom.toFloat() maxY = insets.boundingRectTop.bottom.toFloat()
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) }
if (insets.boundingRectRight.left != 0 &&
insets.boundingRectRight.left > maxX / 2
) {
maxX = insets.boundingRectRight.left.toFloat() maxX = insets.boundingRectRight.left.toFloat()
}
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom

View file

@ -133,7 +133,10 @@ class InputOverlayDrawableDpad(
downButtonState = axisY > VIRT_AXIS_DEADZONE downButtonState = axisY > VIRT_AXIS_DEADZONE
leftButtonState = axisX < -VIRT_AXIS_DEADZONE leftButtonState = axisX < -VIRT_AXIS_DEADZONE
rightButtonState = axisX > VIRT_AXIS_DEADZONE rightButtonState = axisX > VIRT_AXIS_DEADZONE
return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState return oldUpState != upButtonState ||
oldDownState != downButtonState ||
oldLeftState != leftButtonState ||
oldRightState != rightButtonState
} }
return false return false
} }

View file

@ -9,12 +9,12 @@ import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.view.MotionEvent import android.view.MotionEvent
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
import kotlin.math.atan2 import kotlin.math.atan2
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
import kotlin.math.sqrt import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
/** /**
* Custom [BitmapDrawable] that is capable * Custom [BitmapDrawable] that is capable
@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick(
private fun setInnerBounds() { private fun setInnerBounds() {
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
if (x > virtBounds.centerX() + virtBounds.width() / 2) x = if (x > virtBounds.centerX() + virtBounds.width() / 2) {
x =
virtBounds.centerX() + virtBounds.width() / 2 virtBounds.centerX() + virtBounds.width() / 2
if (x < virtBounds.centerX() - virtBounds.width() / 2) x = }
if (x < virtBounds.centerX() - virtBounds.width() / 2) {
x =
virtBounds.centerX() - virtBounds.width() / 2 virtBounds.centerX() - virtBounds.width() / 2
if (y > virtBounds.centerY() + virtBounds.height() / 2) y = }
if (y > virtBounds.centerY() + virtBounds.height() / 2) {
y =
virtBounds.centerY() + virtBounds.height() / 2 virtBounds.centerY() + virtBounds.height() / 2
if (y < virtBounds.centerY() - virtBounds.height() / 2) y = }
if (y < virtBounds.centerY() - virtBounds.height() / 2) {
y =
virtBounds.centerY() - virtBounds.height() / 2 virtBounds.centerY() - virtBounds.height() / 2
}
val width = pressedStateInnerBitmap.bounds.width() / 2 val width = pressedStateInnerBitmap.bounds.width() / 2
val height = pressedStateInnerBitmap.bounds.height() / 2 val height = pressedStateInnerBitmap.bounds.height() / 2
defaultStateInnerBitmap.setBounds( defaultStateInnerBitmap.setBounds(

View file

@ -99,7 +99,9 @@ class GamesFragment : Fragment() {
} }
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
if (shouldSwapData) { if (shouldSwapData) {
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!) (binding.gridGames.adapter as GameAdapter).submitList(
gamesViewModel.games.value!!
)
gamesViewModel.setShouldSwapData(false) gamesViewModel.setShouldSwapData(false)
} }
} }
@ -128,7 +130,9 @@ class GamesFragment : Fragment() {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)

View file

@ -26,6 +26,9 @@ import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import java.io.File
import java.io.FilenameFilter
import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -43,9 +46,6 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.* import org.yuzu.yuzu_emu.utils.*
import java.io.File
import java.io.FilenameFilter
import java.io.IOException
class MainActivity : AppCompatActivity(), ThemeProvider { class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@ -86,7 +86,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
ThemeHelper.SYSTEM_BAR_ALPHA ThemeHelper.SYSTEM_BAR_ALPHA
) )
) )
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
binding.navigationBarShade.setBackgroundColor( binding.navigationBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity( ThemeHelper.getColorWithOpacity(
MaterialColors.getColor( MaterialColors.getColor(
@ -172,7 +174,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding.navigationView.height.toFloat() * 2 binding.navigationView.height.toFloat() * 2
translationY(0f) translationY(0f)
} else { } else {
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { if (ViewCompat.getLayoutDirection(binding.navigationView) ==
ViewCompat.LAYOUT_DIRECTION_LTR
) {
binding.navigationView.translationX = binding.navigationView.translationX =
binding.navigationView.width.toFloat() * -2 binding.navigationView.width.toFloat() * -2
translationX(0f) translationX(0f)
@ -189,7 +193,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (smallLayout) { if (smallLayout) {
translationY(binding.navigationView.height.toFloat() * 2) translationY(binding.navigationView.height.toFloat() * 2)
} else { } else {
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { if (ViewCompat.getLayoutDirection(binding.navigationView) ==
ViewCompat.LAYOUT_DIRECTION_LTR
) {
translationX(binding.navigationView.width.toFloat() * -2) translationX(binding.navigationView.width.toFloat() * -2)
} else { } else {
translationX(binding.navigationView.width.toFloat() * 2) translationX(binding.navigationView.width.toFloat() * 2)
@ -234,7 +240,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
mlpStatusShade.height = insets.top mlpStatusShade.height = insets.top
@ -256,8 +264,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getGamesDirectory = val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) if (result == null) {
return@registerForActivityResult return@registerForActivityResult
}
contentResolver.takePersistableUriPermission( contentResolver.takePersistableUriPermission(
result, result,
@ -281,8 +290,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getProdKey = val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) if (result == null) {
return@registerForActivityResult return@registerForActivityResult
}
if (!FileUtil.hasExtension(result, "keys")) { if (!FileUtil.hasExtension(result, "keys")) {
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
@ -324,8 +334,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getFirmware = val getFirmware =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) if (result == null) {
return@registerForActivityResult return@registerForActivityResult
}
val inputZip = contentResolver.openInputStream(result) val inputZip = contentResolver.openInputStream(result)
if (inputZip == null) { if (inputZip == null) {
@ -376,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getAmiiboKey = val getAmiiboKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) if (result == null) {
return@registerForActivityResult return@registerForActivityResult
}
if (!FileUtil.hasExtension(result, "bin")) { if (!FileUtil.hasExtension(result, "bin")) {
MessageDialogFragment.newInstance( MessageDialogFragment.newInstance(
@ -418,8 +430,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getDriver = val getDriver =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) if (result == null) {
return@registerForActivityResult return@registerForActivityResult
}
val takeFlags = val takeFlags =
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
@ -470,8 +483,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val installGameUpdate = val installGameUpdate =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { registerForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it == null) if (it == null) {
return@registerForActivityResult return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance( IndeterminateProgressDialogFragment.newInstance(
this@MainActivity, this@MainActivity,

View file

@ -19,7 +19,9 @@ class ControllerMappingHelper {
// The two analog triggers generate analog motion events as well as a keycode. // The two analog triggers generate analog motion events as well as a keycode.
// We always prefer to use the analog values, so throw away the button press // We always prefer to use the analog values, so throw away the button press
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
} else false } else {
false
}
} }
/** /**

View file

@ -4,8 +4,8 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.content.Context import android.content.Context
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
object DirectoryInitialization { object DirectoryInitialization {
private var userPath: String? = null private var userPath: String? = null

View file

@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils
import android.net.Uri import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import java.io.File import java.io.File
import java.util.* import java.util.*
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
class DocumentsTree { class DocumentsTree {
private var root: DocumentsNode? = null private var root: DocumentsNode? = null
@ -29,13 +29,20 @@ class DocumentsTree {
val node = resolvePath(filepath) val node = resolvePath(filepath)
return if (node == null || node.isDirectory) { return if (node == null || node.isDirectory) {
0 0
} else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) } else {
FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
}
} }
fun exists(filepath: String): Boolean { fun exists(filepath: String): Boolean {
return resolvePath(filepath) != null return resolvePath(filepath) != null
} }
fun isDirectory(filepath: String): Boolean {
val node = resolvePath(filepath)
return node != null && node.isDirectory
}
private fun resolvePath(filepath: String): DocumentsNode? { private fun resolvePath(filepath: String): DocumentsNode? {
val tokens = StringTokenizer(filepath, File.separator, false) val tokens = StringTokenizer(filepath, File.separator, false)
var iterator = root var iterator = root
@ -106,7 +113,9 @@ class DocumentsTree {
fun isNativePath(path: String): Boolean { fun isNativePath(path: String): Boolean {
return if (path.isNotEmpty()) { return if (path.isNotEmpty()) {
path[0] == '/' path[0] == '/'
} else false } else {
false
}
} }
} }
} }

View file

@ -9,8 +9,6 @@ import android.net.Uri
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.provider.OpenableColumns import android.provider.OpenableColumns
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@ -19,6 +17,8 @@ import java.io.InputStream
import java.net.URLDecoder import java.net.URLDecoder
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object FileUtil { object FileUtil {
const val PATH_TREE = "tree" const val PATH_TREE = "tree"

View file

@ -54,7 +54,7 @@ class ForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) { if (intent == null) {
return START_NOT_STICKY; return START_NOT_STICKY
} }
if (intent.action == ACTION_STOP) { if (intent.action == ACTION_STOP) {
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)

View file

@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.util.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import java.util.*
object GameHelper { object GameHelper {
const val KEY_GAME_PATH = "game_path" const val KEY_GAME_PATH = "game_path"

View file

@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
object GpuDriverHelper { object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json" private const val META_JSON_FILENAME = "meta.json"

View file

@ -3,12 +3,12 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException import java.io.IOException
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import org.json.JSONException
import org.json.JSONObject
class GpuDriverMetadata(metadataFilePath: String) { class GpuDriverMetadata(metadataFilePath: String) {
var name: String? = null var name: String? = null

View file

@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import org.yuzu.yuzu_emu.NativeLibrary
import kotlin.math.sqrt import kotlin.math.sqrt
import org.yuzu.yuzu_emu.NativeLibrary
class InputHandler { class InputHandler {
fun initialize() { fun initialize() {
@ -68,7 +68,11 @@ class InputHandler {
6 -> NativeLibrary.Player6Device 6 -> NativeLibrary.Player6Device
7 -> NativeLibrary.Player7Device 7 -> NativeLibrary.Player7Device
8 -> NativeLibrary.Player8Device 8 -> NativeLibrary.Player8Device
else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device else -> if (NativeLibrary.isHandheldOnly()) {
NativeLibrary.ConsoleDevice
} else {
NativeLibrary.Player1Device
}
} }
} }
@ -107,7 +111,11 @@ class InputHandler {
} }
private fun getAxisToButton(axis: Float): Int { private fun getAxisToButton(axis: Float): Int {
return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED return if (axis > 0.5f) {
NativeLibrary.ButtonState.PRESSED
} else {
NativeLibrary.ButtonState.RELEASED
}
} }
private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
@ -287,7 +295,6 @@ class InputHandler {
} }
} }
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
// Joycon support is half dead. Right joystick doesn't work // Joycon support is half dead. Right joystick doesn't work
val playerNumber = getPlayerNumber(event.device.controllerNumber) val playerNumber = getPlayerNumber(event.device.controllerNumber)
@ -355,6 +362,4 @@ class InputHandler {
) )
} }
} }
} }

View file

@ -4,9 +4,7 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Rect
object InsetsHelper { object InsetsHelper {
const val THREE_BUTTON_NAVIGATION = 0 const val THREE_BUTTON_NAVIGATION = 0
@ -20,12 +18,8 @@ object InsetsHelper {
resources.getIdentifier("config_navBarInteractionMode", "integer", "android") resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
return if (resourceId != 0) { return if (resourceId != 0) {
resources.getInteger(resourceId) resources.getInteger(resourceId)
} else 0 } else {
0
} }
fun getBottomPaddingRequired(activity: Activity): Int {
val visibleFrame = Rect()
activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame)
return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels
} }
} }

View file

@ -13,8 +13,8 @@ import android.nfc.tech.NfcA
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
class NfcReader(private val activity: Activity) { class NfcReader(private val activity: Activity) {
private var nfcAdapter: NfcAdapter? = null private var nfcAdapter: NfcAdapter? = null
@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) {
pendingIntent = PendingIntent.getActivity( pendingIntent = PendingIntent.getActivity(
activity, activity,
0, Intent(activity, activity.javaClass), 0,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) Intent(activity, activity.javaClass),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
else PendingIntent.FLAG_UPDATE_CURRENT } else {
PendingIntent.FLAG_UPDATE_CURRENT
}
) )
val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) {
fun onNewIntent(intent: Intent) { fun onNewIntent(intent: Intent) {
val action = intent.action val action = intent.action
if (NfcAdapter.ACTION_TAG_DISCOVERED != action if (NfcAdapter.ACTION_TAG_DISCOVERED != action &&
&& NfcAdapter.ACTION_TECH_DISCOVERED != action NfcAdapter.ACTION_TECH_DISCOVERED != action &&
&& NfcAdapter.ACTION_NDEF_DISCOVERED != action NfcAdapter.ACTION_NDEF_DISCOVERED != action
) { ) {
return return
} }
@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) {
} }
private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { private fun ntag215ReadAll(amiibo: NfcA): ByteArray? {
val bufferSize = amiibo.maxTransceiveLength; val bufferSize = amiibo.maxTransceiveLength
val tagSize = 0x21C val tagSize = 0x21C
val pageSize = 4 val pageSize = 4
val lastPage = tagSize / pageSize - 1 val lastPage = tagSize / pageSize - 1
@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) {
val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1)
System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize)
} catch (e: IOException) { } catch (e: IOException) {
return null; return null
} }
} }
return tagData return tagData

View file

@ -11,30 +11,34 @@ import java.io.Serializable
object SerializableHelper { object SerializableHelper {
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializable(key, T::class.java) getSerializable(key, T::class.java)
else } else {
getSerializable(key) as? T getSerializable(key) as? T
} }
}
inline fun <reified T : Serializable> Intent.serializable(key: String): T? { inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializableExtra(key, T::class.java) getSerializableExtra(key, T::class.java)
else } else {
getSerializableExtra(key) as? T getSerializableExtra(key) as? T
} }
}
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(key, T::class.java) getParcelable(key, T::class.java)
else } else {
getParcelable(key) as? T getParcelable(key) as? T
} }
}
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(key, T::class.java) getParcelableExtra(key, T::class.java)
else } else {
getParcelableExtra(key) as? T getParcelableExtra(key) as? T
} }
}
} }

View file

@ -3,21 +3,19 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.app.Activity
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.ui.main.ThemeProvider import org.yuzu.yuzu_emu.ui.main.ThemeProvider
import kotlin.math.roundToInt
object ThemeHelper { object ThemeHelper {
const val SYSTEM_BAR_ALPHA = 0.9f const val SYSTEM_BAR_ALPHA = 0.9f
@ -36,8 +34,8 @@ object ThemeHelper {
// Using a specific night mode check because this could apply incorrectly when using the // Using a specific night mode check because this could apply incorrectly when using the
// light app mode, dark system mode, and black backgrounds. Launching the settings activity // light app mode, dark system mode, and black backgrounds. Launching the settings activity
// will then show light mode colors/navigation bars but with black backgrounds. // will then show light mode colors/navigation bars but with black backgrounds.
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
&& isNightMode(activity) isNightMode(activity)
) { ) {
activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
} }
@ -46,8 +44,10 @@ object ThemeHelper {
@ColorInt @ColorInt
fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
return Color.argb( return Color.argb(
(alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color), (alphaFactor * Color.alpha(color)).roundToInt(),
Color.green(color), Color.blue(color) Color.red(color),
Color.green(color),
Color.blue(color)
) )
} }

View file

@ -38,8 +38,8 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
newWidth = width newWidth = width
newHeight = (width / aspectRatio).roundToInt() newHeight = (width / aspectRatio).roundToInt()
} }
val left = (width - newWidth) / 2; val left = (width - newWidth) / 2
val top = (height - newHeight) / 2; val top = (height - newHeight) / 2
setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
} else { } else {
setLeftTopRightBottom(0, 0, width, height) setLeftTopRightBottom(0, 0, width, height)

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<!-- General application strings --> <!-- General application strings -->
<string name="app_name" translatable="false">yuzu</string> <string name="app_name" translatable="false">yuzu</string>

View file

@ -3,6 +3,9 @@
#include "common/fs/file.h" #include "common/fs/file.h"
#include "common/fs/fs.h" #include "common/fs/fs.h"
#ifdef ANDROID
#include "common/fs/fs_android.h"
#endif
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -525,15 +528,39 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
// Generic Filesystem Operations // Generic Filesystem Operations
bool Exists(const fs::path& path) { bool Exists(const fs::path& path) {
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return Android::Exists(path);
} else {
return fs::exists(path); return fs::exists(path);
}
#else
return fs::exists(path);
#endif
} }
bool IsFile(const fs::path& path) { bool IsFile(const fs::path& path) {
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return !Android::IsDirectory(path);
} else {
return fs::is_regular_file(path); return fs::is_regular_file(path);
}
#else
return fs::is_regular_file(path);
#endif
} }
bool IsDir(const fs::path& path) { bool IsDir(const fs::path& path) {
#ifdef ANDROID
if (Android::IsContentUri(path)) {
return Android::IsDirectory(path);
} else {
return fs::is_directory(path); return fs::is_directory(path);
}
#else
return fs::is_directory(path);
#endif
} }
fs::path GetCurrentDir() { fs::path GetCurrentDir() {

View file

@ -12,7 +12,10 @@
"openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I")
#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \
V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \
"(Ljava/lang/String;)Z") \
V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
namespace Common::FS::Android { namespace Common::FS::Android {

View file

@ -82,6 +82,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
switch (nfc_file.GetSize()) { switch (nfc_file.GetSize()) {
case AmiiboSize: case AmiiboSize:
case AmiiboSizeWithoutPassword: case AmiiboSizeWithoutPassword:
case AmiiboSizeWithSignature:
data.resize(AmiiboSize); data.resize(AmiiboSize);
if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) {
return Info::NotAnAmiibo; return Info::NotAnAmiibo;
@ -109,6 +110,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) {
switch (data.size_bytes()) { switch (data.size_bytes()) {
case AmiiboSize: case AmiiboSize:
case AmiiboSizeWithoutPassword: case AmiiboSizeWithoutPassword:
case AmiiboSizeWithSignature:
nfc_data.resize(AmiiboSize); nfc_data.resize(AmiiboSize);
break; break;
case MifareSize: case MifareSize:

View file

@ -57,6 +57,7 @@ public:
private: private:
static constexpr std::size_t AmiiboSize = 0x21C; static constexpr std::size_t AmiiboSize = 0x21C;
static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8;
static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20;
static constexpr std::size_t MifareSize = 0x400; static constexpr std::size_t MifareSize = 0x400;
std::string file_path{}; std::string file_path{};