early-access version 3684
This commit is contained in:
parent
915fe36508
commit
cff9ef374b
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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()
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -58,11 +58,12 @@ 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 ->
|
||||||
ContextCompat.getDrawable(
|
binding.optionLayout.background =
|
||||||
binding.optionCard.context,
|
ContextCompat.getDrawable(
|
||||||
R.drawable.premium_background
|
binding.optionCard.context,
|
||||||
)
|
R.drawable.premium_background
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,19 +40,22 @@ 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(
|
||||||
override fun run() {
|
object : Runnable {
|
||||||
val insets = ViewCompat.getRootWindowInsets(overlayView)
|
override fun run() {
|
||||||
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
|
val insets = ViewCompat.getRootWindowInsets(overlayView)
|
||||||
if (isKeyboardVisible) {
|
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
|
||||||
handler.postDelayed(this, delayMs.toLong())
|
if (isKeyboardVisible) {
|
||||||
return
|
handler.postDelayed(this, delayMs.toLong())
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -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() }
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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,11 +132,13 @@ 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,8 +149,9 @@ 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?) {
|
||||||
@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
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,24 +277,30 @@ 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 {
|
||||||
@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() {
|
|||||||
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"
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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?,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
@ -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!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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) {
|
||||||
|
@ -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) :
|
||||||
|
@ -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 {
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
@ -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,25 +329,30 @@ 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,
|
||||||
if (it.isSeparating) {
|
newLayoutInfo: WindowLayoutInfo
|
||||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
) {
|
||||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
val isFolding =
|
||||||
// Restrict emulation and overlays to the top of the screen
|
(newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||||
binding.emulationContainer.layoutParams.height = it.bounds.top
|
if (it.isSeparating) {
|
||||||
binding.overlayContainer.layoutParams.height = it.bounds.top
|
emulationActivity.requestedOrientation =
|
||||||
// Restrict input and menu drawer to the bottom of the screen
|
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||||
binding.inputContainer.layoutParams.height = it.bounds.bottom
|
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
||||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
// Restrict emulation and overlays to the top of the screen
|
||||||
|
binding.emulationContainer.layoutParams.height = it.bounds.top
|
||||||
|
binding.overlayContainer.layoutParams.height = it.bounds.top
|
||||||
|
// Restrict input and menu drawer to the bottom of the screen
|
||||||
|
binding.inputContainer.layoutParams.height = it.bounds.bottom
|
||||||
|
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||||
|
|
||||||
isInFoldableLayout = true
|
isInFoldableLayout = true
|
||||||
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
|
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
|
||||||
refreshInputOverlay()
|
refreshInputOverlay()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
it.isSeparating
|
||||||
it.isSeparating
|
} ?: false
|
||||||
} ?: false
|
|
||||||
if (!isFolding) {
|
if (!isFolding) {
|
||||||
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
@ -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(
|
||||||
inputScaleValue.text = "${value.toInt()}%"
|
Slider.OnChangeListener { _, value, _ ->
|
||||||
setControlScale(value.toInt())
|
inputScaleValue.text = "${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(
|
||||||
inputOpacityValue.text = "${value.toInt()}%"
|
Slider.OnChangeListener { _, value, _ ->
|
||||||
setControlOpacity(value.toInt())
|
inputOpacityValue.text = "${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."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() {
|
|||||||
val outputZipFile = File(
|
val outputZipFile = File(
|
||||||
tempFolder,
|
tempFolder,
|
||||||
"yuzu saves - ${
|
"yuzu saves - ${
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||||
}.zip"
|
}.zip"
|
||||||
)
|
)
|
||||||
outputZipFile.createNewFile()
|
outputZipFile.createNewFile()
|
||||||
@ -106,12 +106,14 @@ 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
|
||||||
@ -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}"
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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 {
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
@ -243,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
// If no button is being moved now, remember the currently touched button to move.
|
// If no button is being moved now, remember the currently touched button to move.
|
||||||
if (buttonBeingConfigured == null &&
|
if (buttonBeingConfigured == null &&
|
||||||
button.bounds.contains(
|
button.bounds.contains(
|
||||||
fingerPositionX,
|
fingerPositionX,
|
||||||
fingerPositionY
|
fingerPositionY
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
buttonBeingConfigured = button
|
buttonBeingConfigured = button
|
||||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||||
@ -309,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
MotionEvent.ACTION_DOWN,
|
MotionEvent.ACTION_DOWN,
|
||||||
MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
|
MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
|
||||||
joystick.bounds.contains(
|
joystick.bounds.contains(
|
||||||
fingerPositionX,
|
fingerPositionX,
|
||||||
fingerPositionY
|
fingerPositionY
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
joystickBeingConfigured = joystick
|
joystickBeingConfigured = joystick
|
||||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
joystickBeingConfigured!!.onConfigureTouch(event)
|
||||||
@ -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
|
||||||
@ -820,7 +825,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
* @param context Context for getting the vector drawable
|
* @param context Context for getting the vector drawable
|
||||||
* @param drawableId The ID of the drawable to scale.
|
* @param drawableId The ID of the drawable to scale.
|
||||||
* @param scale The scale factor for the bitmap.
|
* @param scale The scale factor for the bitmap.
|
||||||
* @return The scaled [Bitmap]
|
* @return The scaled [Bitmap]
|
||||||
*/
|
*/
|
||||||
private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
|
private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
|
||||||
val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
|
val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
|
||||||
@ -854,7 +859,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
* Gets the safe screen size for drawing the overlay
|
* Gets the safe screen size for drawing the overlay
|
||||||
*
|
*
|
||||||
* @param context Context for getting the window metrics
|
* @param context Context for getting the window metrics
|
||||||
* @return A pair of points, the first being the top left corner of the safe area,
|
* @return A pair of points, the first being the top left corner of the safe area,
|
||||||
* the second being the bottom right corner of the safe area
|
* the second being the bottom right corner of the safe area
|
||||||
*/
|
*/
|
||||||
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
||||||
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
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 (x < virtBounds.centerX() - virtBounds.width() / 2) {
|
||||||
virtBounds.centerY() + virtBounds.height() / 2
|
x =
|
||||||
if (y < virtBounds.centerY() - virtBounds.height() / 2) y =
|
virtBounds.centerX() - virtBounds.width() / 2
|
||||||
virtBounds.centerY() - virtBounds.height() / 2
|
}
|
||||||
|
if (y > virtBounds.centerY() + virtBounds.height() / 2) {
|
||||||
|
y =
|
||||||
|
virtBounds.centerY() + virtBounds.height() / 2
|
||||||
|
}
|
||||||
|
if (y < virtBounds.centerY() - virtBounds.height() / 2) {
|
||||||
|
y =
|
||||||
|
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(
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
#else
|
||||||
return fs::exists(path);
|
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);
|
||||||
|
}
|
||||||
|
#else
|
||||||
return fs::is_regular_file(path);
|
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);
|
||||||
|
}
|
||||||
|
#else
|
||||||
return fs::is_directory(path);
|
return fs::is_directory(path);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::path GetCurrentDir() {
|
fs::path GetCurrentDir() {
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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{};
|
||||||
|
Loading…
Reference in New Issue
Block a user