[android]: (feat) emuready launch intent + redesign (#162)

feat: emuready intent support and slight redesign
Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: eden-emu/eden#162
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: Producdevity <y.gherbi.dev@gmail.com>
Co-committed-by: Producdevity <y.gherbi.dev@gmail.com>
This commit is contained in:
Producdevity 2025-08-03 17:03:53 +02:00 committed by crueter
parent 3b72c29303
commit f72783e017
Signed by untrusted user: crueter
GPG key ID: 425ACD2D4830EBC6
106 changed files with 2264 additions and 697 deletions

View file

@ -156,7 +156,6 @@ android {
} }
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
version = "3.22.1" version = "3.22.1"

View file

@ -12,7 +12,7 @@ SPDX-FileCopyrightText: Eden Emulator Project
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" /> <uses-feature android:name="android.hardware.gamepad" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />
@ -42,6 +42,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:banner="@drawable/tv_banner" android:banner="@drawable/tv_banner"
android:fullBackupContent="@xml/data_extraction_rules" android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31" android:dataExtractionRules="@xml/data_extraction_rules_api_31"
tools:targetApi="33"
android:enableOnBackInvokedCallback="true"> android:enableOnBackInvokedCallback="true">
<meta-data android:name="com.samsung.android.gamehub" android:value="true" /> <meta-data android:name="com.samsung.android.gamehub" android:value="true" />
@ -85,6 +86,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/octet-stream" android:scheme="content"/> <data android:mimeType="application/octet-stream" android:scheme="content"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="dev.eden.eden_emulator.LAUNCH_WITH_CUSTOM_CONFIG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
</activity> </activity>

View file

@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu package org.yuzu.yuzu_emu
import android.content.DialogInterface import android.content.DialogInterface
@ -17,7 +16,6 @@ import android.widget.TextView
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.core.net.toUri import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import net.swiftzer.semver.SemVer
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
@ -28,7 +26,6 @@ import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.Patch import org.yuzu.yuzu_emu.model.Patch
import org.yuzu.yuzu_emu.model.GameVerificationResult import org.yuzu.yuzu_emu.model.GameVerificationResult
import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.network.NetPlayManager
import java.io.File
/** /**
* Class which contains methods that interact * Class which contains methods that interact
@ -276,8 +273,7 @@ object NativeLibrary {
val emulationActivity = sEmulationActivity.get() val emulationActivity = sEmulationActivity.get()
if (emulationActivity != null) { if (emulationActivity != null) {
emulationActivity.addNetPlayMessages(type, message) emulationActivity.addNetPlayMessages(type, message)
} } else {
else {
NetPlayManager.addNetPlayMessage(type, message) NetPlayManager.addNetPlayMessage(type, message)
} }
} }

View file

@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.activities package org.yuzu.yuzu_emu.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
@ -58,7 +57,6 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NfcReader import org.yuzu.yuzu_emu.utils.NfcReader
import org.yuzu.yuzu_emu.utils.ParamPackage import org.yuzu.yuzu_emu.utils.ParamPackage
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import org.yuzu.yuzu_emu.utils.PowerStateUtils
import java.text.NumberFormat import java.text.NumberFormat
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -204,6 +202,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
setIntent(intent) setIntent(intent)
// Reset navigation graph with new intent data to recreate EmulationFragment
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
nfcReader.onNewIntent(intent) nfcReader.onNewIntent(intent)
InputHandler.updateControllerData() InputHandler.updateControllerData()
} }
@ -421,7 +425,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
NetPlayManager.addNetPlayMessage(type, msg) NetPlayManager.addNetPlayMessage(type, msg)
} }
private var pictureInPictureReceiver = object : BroadcastReceiver() { private var pictureInPictureReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) { override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == actionPlay) { if (intent.action == actionPlay) {

View file

@ -4,15 +4,10 @@
package org.yuzu.yuzu_emu.adapters package org.yuzu.yuzu_emu.adapters
import android.content.DialogInterface import android.content.DialogInterface
import android.net.Uri
import android.text.Html import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutInfoCompat
@ -37,7 +32,6 @@ import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
import androidx.recyclerview.widget.RecyclerView
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.content.edit import androidx.core.content.edit
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -94,7 +88,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
} }
VIEW_TYPE_CAROUSEL -> { VIEW_TYPE_CAROUSEL -> {
val carouselBinding = holder.binding as CardGameCarouselBinding val carouselBinding = holder.binding as CardGameCarouselBinding
//soothens transient flickering // soothens transient flickering
carouselBinding.cardGameCarousel.scaleY = 0f carouselBinding.cardGameCarousel.scaleY = 0f
carouselBinding.cardGameCarousel.alpha = 0f carouselBinding.cardGameCarousel.alpha = 0f
} }
@ -103,9 +97,21 @@ class GameAdapter(private val activity: AppCompatActivity) :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder {
val binding = when (viewType) { val binding = when (viewType) {
VIEW_TYPE_LIST -> CardGameListBinding.inflate(LayoutInflater.from(parent.context), parent, false) VIEW_TYPE_LIST -> CardGameListBinding.inflate(
VIEW_TYPE_GRID -> CardGameGridBinding.inflate(LayoutInflater.from(parent.context), parent, false) LayoutInflater.from(parent.context),
VIEW_TYPE_CAROUSEL -> CardGameCarouselBinding.inflate(LayoutInflater.from(parent.context), parent, false) parent,
false
)
VIEW_TYPE_GRID -> CardGameGridBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
VIEW_TYPE_CAROUSEL -> CardGameCarouselBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
else -> throw IllegalArgumentException("Invalid view type") else -> throw IllegalArgumentException("Invalid view type")
} }
return GameViewHolder(binding, viewType) return GameViewHolder(binding, viewType)
@ -212,7 +218,10 @@ class GameAdapter(private val activity: AppCompatActivity) :
.setIcon(GameIconUtils.getShortcutIcon(activity, game)) .setIcon(GameIconUtils.getShortcutIcon(activity, game))
.setIntent(game.launchIntent) .setIntent(game.launchIntent)
.build() .build()
ShortcutManagerCompat.pushDynamicShortcut(YuzuApplication.appContext, shortcut) ShortcutManagerCompat.pushDynamicShortcut(
YuzuApplication.appContext,
shortcut
)
} }
} }
@ -232,7 +241,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
launch() launch()
} }
.setNegativeButton(android.R.string.cancel) { _,_ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
.show() .show()
} else { } else {
launch() launch()

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -6,10 +9,8 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting

View file

@ -28,8 +28,7 @@ class ChatMessage(
val username: String, // Username is the community/forum username val username: String, // Username is the community/forum username
val message: String, val message: String,
val timestamp: String = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date()) val timestamp: String = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date())
) { )
}
class ChatDialog(context: Context) : BottomSheetDialog(context) { class ChatDialog(context: Context) : BottomSheetDialog(context) {
private lateinit var binding: DialogChatBinding private lateinit var binding: DialogChatBinding
@ -50,7 +49,8 @@ class ChatDialog(context: Context) : BottomSheetDialog(context) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.state = BottomSheetBehavior.STATE_EXPANDED behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE behavior.skipCollapsed =
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
handler.post { handler.post {
chatAdapter.notifyDataSetChanged() chatAdapter.notifyDataSetChanged()
@ -133,10 +133,12 @@ class ChatAdapter(private val messages: List<ChatMessage>) :
fun bind(message: ChatMessage) { fun bind(message: ChatMessage) {
binding.usernameText.text = message.nickname binding.usernameText.text = message.nickname
binding.messageText.text = message.message binding.messageText.text = message.message
binding.userIcon.setImageResource(when (message.nickname) { binding.userIcon.setImageResource(
when (message.nickname) {
"System" -> R.drawable.ic_system "System" -> R.drawable.ic_system
else -> R.drawable.ic_user else -> R.drawable.ic_user
}) }
)
} }
} }
} }

View file

@ -245,7 +245,6 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) {
it.score it.score
}.map { it.item } }.map { it.item }
adapter.updateRooms(sortedList) adapter.updateRooms(sortedList)
} }
} }

View file

@ -16,7 +16,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Button
import android.widget.EditText
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.Toast import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -38,7 +37,6 @@ import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.network.NetPlayManager import org.yuzu.yuzu_emu.network.NetPlayManager
import org.yuzu.yuzu_emu.utils.CompatUtils import org.yuzu.yuzu_emu.utils.CompatUtils
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import java.net.InetAddress
class NetPlayDialog(context: Context) : BottomSheetDialog(context) { class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
private lateinit var adapter: NetPlayAdapter private lateinit var adapter: NetPlayAdapter
@ -55,7 +53,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
when { when {
NetPlayManager.netPlayIsJoined() -> DialogMultiplayerLobbyBinding.inflate(layoutInflater) NetPlayManager.netPlayIsJoined() -> DialogMultiplayerLobbyBinding.inflate(
layoutInflater
)
.apply { .apply {
setContentView(root) setContentView(root)
adapter = NetPlayAdapter() adapter = NetPlayAdapter()
@ -77,7 +77,6 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
btnModeration.setOnClickListener { btnModeration.setOnClickListener {
showModerationDialog() showModerationDialog()
} }
} }
else -> { else -> {
@ -140,7 +139,8 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
inner class NetPlayAdapter : RecyclerView.Adapter<NetPlayAdapter.NetPlayViewHolder>() { inner class NetPlayAdapter : RecyclerView.Adapter<NetPlayAdapter.NetPlayViewHolder>() {
val netPlayItems = mutableListOf<NetPlayItems>() val netPlayItems = mutableListOf<NetPlayItems>()
abstract inner class NetPlayViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), abstract inner class NetPlayViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView),
View.OnClickListener { View.OnClickListener {
init { init {
itemView.setOnClickListener(this) itemView.setOnClickListener(this)
@ -167,7 +167,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
visibility = if (iconRes != 0) { visibility = if (iconRes != 0) {
setImageResource(iconRes) setImageResource(iconRes)
View.VISIBLE View.VISIBLE
} else View.GONE } else {
View.GONE
}
} }
} }
} }
@ -186,7 +188,6 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun onClick(clicked: View) {} override fun onClick(clicked: View) {}
private fun showPopupMenu(view: View) { private fun showPopupMenu(view: View) {
PopupMenu(view.context, view).apply { PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.menu_netplay_member, menu) menuInflater.inflate(R.menu.menu_netplay_member, menu)
@ -201,7 +202,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
} else if (item.itemId == R.id.action_ban) { } else if (item.itemId == R.id.action_ban) {
NetPlayManager.netPlayBanUser(netPlayItems.name) NetPlayManager.netPlayBanUser(netPlayItems.name)
true true
} else false } else {
false
}
} }
show() show()
} }
@ -360,12 +363,15 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val visibilityList: List<String> = listOf( val visibilityList: List<String> = listOf(
context.getString(R.string.multiplayer_public_visibility), context.getString(R.string.multiplayer_public_visibility),
context.getString(R.string.multiplayer_unlisted_visibility), context.getString(R.string.multiplayer_unlisted_visibility)
) )
binding.textTitle.text = activity.getString( binding.textTitle.text = activity.getString(
if (isCreateRoom) R.string.multiplayer_create_room if (isCreateRoom) {
else R.string.multiplayer_join_room R.string.multiplayer_create_room
} else {
R.string.multiplayer_join_room
}
) )
// setup listeners etc // setup listeners etc
@ -446,7 +452,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
) )
} }
binding.dropdownLobbyVisibility.setText(context.getString(R.string.multiplayer_unlisted_visibility)) binding.dropdownLobbyVisibility.setText(
context.getString(R.string.multiplayer_unlisted_visibility)
)
binding.dropdownLobbyVisibility.apply { binding.dropdownLobbyVisibility.apply {
setAdapter( setAdapter(
@ -501,8 +509,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
binding.btnConfirm.isEnabled = false binding.btnConfirm.isEnabled = false
binding.btnConfirm.text = binding.btnConfirm.text =
activity.getString( activity.getString(
if (isCreateRoom) R.string.multiplayer_creating if (isCreateRoom) {
else R.string.multiplayer_joining R.string.multiplayer_creating
} else {
R.string.multiplayer_joining
}
) )
// We don't need to worry about validation because it's already been done. // We don't need to worry about validation because it's already been done.
@ -546,8 +557,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
Toast.makeText( Toast.makeText(
YuzuApplication.appContext, YuzuApplication.appContext,
if (isCreateRoom) R.string.multiplayer_create_room_success if (isCreateRoom) {
else R.string.multiplayer_join_room_success, R.string.multiplayer_create_room_success
} else {
R.string.multiplayer_join_room_success
},
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
@ -619,7 +633,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemBanListBinding.inflate( val binding = ItemBanListBinding.inflate(
LayoutInflater.from(parent.context), parent, false LayoutInflater.from(parent.context),
parent,
false
) )
return ViewHolder(binding) return ViewHolder(binding)
} }
@ -654,6 +670,5 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
notifyItemRemoved(position) notifyItemRemoved(position)
} }
} }
} }
} }

View file

@ -13,7 +13,6 @@ import org.yuzu.yuzu_emu.databinding.ItemDriverGroupBinding
import org.yuzu.yuzu_emu.fragments.DriverFetcherFragment.DriverGroup import org.yuzu.yuzu_emu.fragments.DriverFetcherFragment.DriverGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.transition.AutoTransition
import androidx.transition.ChangeBounds import androidx.transition.ChangeBounds
import androidx.transition.Fade import androidx.transition.Fade
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
@ -89,7 +88,9 @@ class DriverGroupAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverGroupViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverGroupViewHolder {
val binding = ItemDriverGroupBinding.inflate( val binding = ItemDriverGroupBinding.inflate(
LayoutInflater.from(parent.context), parent, false LayoutInflater.from(parent.context),
parent,
false
) )
return DriverGroupViewHolder(binding) return DriverGroupViewHolder(binding)
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.fetcher package org.yuzu.yuzu_emu.features.fetcher
import android.animation.LayoutTransition import android.animation.LayoutTransition
@ -71,7 +74,7 @@ class ReleaseAdapter(
// truncates to 150 chars so it does not take up too much space. // truncates to 150 chars so it does not take up too much space.
var bodyPreview = release.body.take(150) var bodyPreview = release.body.take(150)
bodyPreview = bodyPreview.replace("#", "").removeSurrounding(" "); bodyPreview = bodyPreview.replace("#", "").removeSurrounding(" ")
val body = val body =
bodyPreview.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\n", "<br>") bodyPreview.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\n", "<br>")
@ -122,8 +125,11 @@ class ReleaseAdapter(
binding.imageDownloadsArrow.rotation = if (isVisible) 0f else 180f binding.imageDownloadsArrow.rotation = if (isVisible) 0f else 180f
binding.buttonToggleDownloads.text = binding.buttonToggleDownloads.text =
if (isVisible) activity.getString(R.string.show_downloads) if (isVisible) {
else activity.getString(R.string.hide_downloads) activity.getString(R.string.show_downloads)
} else {
activity.getString(R.string.hide_downloads)
}
} }
binding.buttonToggleDownloads.setOnClickListener { binding.buttonToggleDownloads.setOnClickListener {
@ -139,9 +145,15 @@ class ReleaseAdapter(
release.artifacts.forEach { artifact -> release.artifacts.forEach { artifact ->
val button = MaterialButton(binding.root.context).apply { val button = MaterialButton(binding.root.context).apply {
text = artifact.name text = artifact.name
setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelLarge) setTextAppearance(
com.google.android.material.R.style.TextAppearance_Material3_LabelLarge
)
textAlignment = MaterialButton.TEXT_ALIGNMENT_VIEW_START textAlignment = MaterialButton.TEXT_ALIGNMENT_VIEW_START
setBackgroundColor(context.getColor(com.google.android.material.R.color.m3_button_background_color_selector)) setBackgroundColor(
context.getColor(
com.google.android.material.R.color.m3_button_background_color_selector
)
)
setIconResource(R.drawable.ic_import) setIconResource(R.drawable.ic_import)
iconTint = ColorStateList.valueOf( iconTint = ColorStateList.valueOf(
MaterialColors.getColor( MaterialColors.getColor(
@ -199,7 +211,9 @@ class ReleaseAdapter(
input.copyTo(output) input.copyTo(output)
} }
} }
?: throw IOException(context.getString(R.string.empty_response_body)) ?: throw IOException(
context.getString(R.string.empty_response_body)
)
} }
} }
@ -211,7 +225,9 @@ class ReleaseAdapter(
val driverData = GpuDriverHelper.getMetadataFromZip(file) val driverData = GpuDriverHelper.getMetadataFromZip(file)
val driverPath = val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(file.toUri())}" "${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(
file.toUri()
)}"
if (GpuDriverHelper.copyDriverToInternalStorage(file.toUri())) { if (GpuDriverHelper.copyDriverToInternalStorage(file.toUri())) {
driverViewModel.onDriverAdded(Pair(driverPath, driverData)) driverViewModel.onDriverAdded(Pair(driverPath, driverData))
@ -254,7 +270,9 @@ class ReleaseAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReleaseViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReleaseViewHolder {
val binding = ItemReleaseBinding.inflate( val binding = ItemReleaseBinding.inflate(
LayoutInflater.from(parent.context), parent, false LayoutInflater.from(parent.context),
parent,
false
) )
return ReleaseViewHolder(binding) return ReleaseViewHolder(binding)
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.fetcher package org.yuzu.yuzu_emu.features.fetcher
import android.graphics.Rect import android.graphics.Rect

View file

@ -64,13 +64,12 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
SHOW_SHADERS_BUILDING("show_shaders_building"), SHOW_SHADERS_BUILDING("show_shaders_building"),
DEBUG_FLUSH_BY_LINE("flush_lines"), DEBUG_FLUSH_BY_LINE("flush_lines"),
USE_LRU_CACHE("use_lru_cache"),; USE_LRU_CACHE("use_lru_cache");
external fun isRaiiEnabled(): Boolean external fun isRaiiEnabled(): Boolean
// external fun isFrameSkippingEnabled(): Boolean // external fun isFrameSkippingEnabled(): Boolean
external fun isFrameInterpolationEnabled(): Boolean external fun isFrameInterpolationEnabled(): Boolean
override fun getBoolean(needsGlobal: Boolean): Boolean = override fun getBoolean(needsGlobal: Boolean): Boolean =
NativeConfig.getBoolean(key, needsGlobal) NativeConfig.getBoolean(key, needsGlobal)

View file

@ -57,7 +57,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
OFFLINE_WEB_APPLET("offline_web_applet_mode"), OFFLINE_WEB_APPLET("offline_web_applet_mode"),
LOGIN_SHARE_APPLET("login_share_applet_mode"), LOGIN_SHARE_APPLET("login_share_applet_mode"),
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"), WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
MY_PAGE_APPLET("my_page_applet_mode"), MY_PAGE_APPLET("my_page_applet_mode")
; ;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)

View file

@ -13,7 +13,7 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
DEVICE_NAME("device_name"), DEVICE_NAME("device_name"),
WEB_TOKEN("eden_token"), WEB_TOKEN("eden_token"),
WEB_USERNAME("eden_username"), WEB_USERNAME("eden_username")
; ;
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal) override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)

View file

@ -21,7 +21,6 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.network.NetDataValidators import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeConfig
/** /**
@ -516,7 +515,6 @@ abstract class SettingsItem(
) )
) )
put( put(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.RENDERER_VSYNC, IntSetting.RENDERER_VSYNC,

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -18,7 +21,7 @@ class SingleChoiceSetting(
@ArrayRes val choicesId: Int, @ArrayRes val choicesId: Int,
@ArrayRes val valuesId: Int, @ArrayRes val valuesId: Int,
val warnChoices: List<Int> = ArrayList(), val warnChoices: List<Int> = ArrayList(),
@StringRes val warningMessage: Int = 0, @StringRes val warningMessage: Int = 0
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { ) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SINGLE_CHOICE override val type = TYPE_SINGLE_CHOICE

View file

@ -6,7 +6,6 @@
package org.yuzu.yuzu_emu.features.settings.model.view package org.yuzu.yuzu_emu.features.settings.model.view
import android.text.Editable
import androidx.annotation.StringRes import androidx.annotation.StringRes
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting

View file

@ -179,7 +179,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {
val isValid = validator(s.toString()) val isValid = validator(s.toString())
stringInputBinding.editTextLayout.isErrorEnabled = !isValid stringInputBinding.editTextLayout.isErrorEnabled = !isValid
stringInputBinding.editTextLayout.error = if (isValid) null else requireContext().getString(item.errorId) stringInputBinding.editTextLayout.error = if (isValid) {
null
} else {
requireContext().getString(
item.errorId
)
}
} }
} }

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -8,7 +11,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.edit
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -16,13 +18,11 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins

View file

@ -4,8 +4,6 @@
package org.yuzu.yuzu_emu.features.settings.ui package org.yuzu.yuzu_emu.features.settings.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -1057,7 +1055,9 @@ class SettingsFragmentPresenter(
} }
val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting { val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting {
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) val preferences = PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
override fun getInt(needsGlobal: Boolean): Int = override fun getInt(needsGlobal: Boolean): Int =
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
override fun setInt(value: Int) { override fun setInt(value: Int) {

View file

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.content.ClipData import android.content.ClipData

View file

@ -33,7 +33,10 @@ class AddGameFolderDialogFragment : DialogFragment() {
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked) val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
homeViewModel.setGamesDirSelected(true) homeViewModel.setGamesDirSelected(true)
val calledFromGameFragment = requireArguments().getBoolean("calledFromGameFragment", false) val calledFromGameFragment = requireArguments().getBoolean(
"calledFromGameFragment",
false
)
gamesViewModel.addFolder(newGameDir, calledFromGameFragment) gamesViewModel.addFolder(newGameDir, calledFromGameFragment)
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)

View file

@ -62,14 +62,14 @@ class DriverFetcherFragment : Fragment() {
val path: String = "", val path: String = "",
val sort: Int = 0, val sort: Int = 0,
val useTagName: Boolean = false, val useTagName: Boolean = false,
val sortMode: SortMode = SortMode.Default, val sortMode: SortMode = SortMode.Default
) )
private val repoList: List<DriverRepo> = listOf( private val repoList: List<DriverRepo> = listOf(
DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0), DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0),
DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1), DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1),
DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true, SortMode.PublishTime), DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true, SortMode.PublishTime),
DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3), DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3)
) )
private val driverMap = listOf( private val driverMap = listOf(
@ -81,7 +81,7 @@ class DriverFetcherFragment : Fragment() {
IntRange(700, 710) to "KIMCHI 25.2.0_r5", IntRange(700, 710) to "KIMCHI 25.2.0_r5",
IntRange(711, 799) to "Mr. Purple T21", IntRange(711, 799) to "Mr. Purple T21",
IntRange(800, 899) to "GameHub Adreno 8xx", IntRange(800, 899) to "GameHub Adreno 8xx",
IntRange(900, Int.MAX_VALUE) to "Unsupported", IntRange(900, Int.MAX_VALUE) to "Unsupported"
) )
private lateinit var driverGroupAdapter: DriverGroupAdapter private lateinit var driverGroupAdapter: DriverGroupAdapter
@ -124,7 +124,9 @@ class DriverFetcherFragment : Fragment() {
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View { ): View {
_binding = FragmentDriverFetcherBinding.inflate(inflater) _binding = FragmentDriverFetcherBinding.inflate(inflater)
binding.badgeRecommendedDriver.text = recommendedDriver binding.badgeRecommendedDriver.text = recommendedDriver
@ -178,8 +180,12 @@ class DriverFetcherFragment : Fragment() {
} }
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(requireActivity()).setTitle(getString(R.string.error_during_fetch)) MaterialAlertDialogBuilder(requireActivity()).setTitle(
.setMessage("${getString(R.string.failed_to_fetch)} ${name}:\n${e.message}") getString(R.string.error_during_fetch)
)
.setMessage(
"${getString(R.string.failed_to_fetch)} $name:\n${e.message}"
)
.setPositiveButton(getString(R.string.ok)) { dialog, _ -> dialog.cancel() } .setPositiveButton(getString(R.string.ok)) { dialog, _ -> dialog.cancel() }
.show() .show()
@ -188,7 +194,9 @@ class DriverFetcherFragment : Fragment() {
} }
val group = DriverGroup( val group = DriverGroup(
name, releases, sort name,
releases,
sort
) )
synchronized(driverGroups) { synchronized(driverGroups) {
@ -223,7 +231,9 @@ class DriverFetcherFragment : Fragment() {
binding.listDrivers.updateMargins(left = leftInsets, right = rightInsets) binding.listDrivers.updateMargins(left = leftInsets, right = rightInsets)
binding.listDrivers.updatePadding( binding.listDrivers.updatePadding(
bottom = barInsets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) bottom = barInsets.bottom + resources.getDimensionPixelSize(
R.dimen.spacing_bottom_list_fab
)
) )
windowInsets windowInsets
@ -239,11 +249,13 @@ class DriverFetcherFragment : Fragment() {
var artifacts: List<Artifact> = ArrayList(), var artifacts: List<Artifact> = ArrayList(),
var prerelease: Boolean = false, var prerelease: Boolean = false,
var latest: Boolean = false, var latest: Boolean = false,
var publishTime: LocalDateTime = LocalDateTime.now(), var publishTime: LocalDateTime = LocalDateTime.now()
) { ) {
companion object { companion object {
fun fromJsonArray( fun fromJsonArray(
jsonString: String, useTagName: Boolean, sortMode: SortMode jsonString: String,
useTagName: Boolean,
sortMode: SortMode
): ArrayList<Release> { ): ArrayList<Release> {
val mapper = jacksonObjectMapper() val mapper = jacksonObjectMapper()
@ -310,7 +322,16 @@ class DriverFetcherFragment : Fragment() {
} }
} }
return Release(tagName, titleName, title, body, artifacts, prerelease, false, localTime) return Release(
tagName,
titleName,
title,
body,
artifacts,
prerelease,
false,
localTime
)
} catch (e: Exception) { } catch (e: Exception) {
// TODO: handle malformed input. // TODO: handle malformed input.
e.printStackTrace() e.printStackTrace()
@ -324,6 +345,6 @@ class DriverFetcherFragment : Fragment() {
data class DriverGroup( data class DriverGroup(
val name: String, val name: String,
val releases: ArrayList<Release>, val releases: ArrayList<Release>,
val sort: Int, val sort: Int
) )
} }

View file

@ -25,7 +25,6 @@ import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.DriverAdapter import org.yuzu.yuzu_emu.adapters.DriverAdapter
import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
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.model.StringSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
@ -108,7 +107,9 @@ class DriverManagerFragment : Fragment() {
} }
binding.buttonFetch.setOnClickListener { binding.buttonFetch.setOnClickListener {
binding.root.findNavController().navigate(R.id.action_driverManagerFragment_to_driverFetcherFragment) binding.root.findNavController().navigate(
R.id.action_driverManagerFragment_to_driverFetcherFragment
)
} }
binding.listDrivers.apply { binding.listDrivers.apply {

View file

@ -45,6 +45,7 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.window.layout.FoldingFeature import androidx.window.layout.FoldingFeature
@ -52,7 +53,6 @@ import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo import androidx.window.layout.WindowLayoutInfo
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.slider.Slider
import com.google.android.material.textview.MaterialTextView import com.google.android.material.textview.MaterialTextView
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
@ -81,6 +81,12 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import java.io.File import java.io.File
class EmulationFragment : Fragment(), SurfaceHolder.Callback { class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@ -90,24 +96,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var perfStatsUpdater: (() -> Unit)? = null private var perfStatsUpdater: (() -> Unit)? = null
private var socUpdater: (() -> Unit)? = null private var socUpdater: (() -> Unit)? = null
private lateinit var cpuBackend: String
private lateinit var gpuDriver: String
private var _binding: FragmentEmulationBinding? = null private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private val args by navArgs<EmulationFragmentArgs>() private val args by navArgs<EmulationFragmentArgs>()
private lateinit var game: Game private var game: Game? = null
private val emulationViewModel: EmulationViewModel by activityViewModels() private val emulationViewModel: EmulationViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels() private val driverViewModel: DriverViewModel by activityViewModels()
private var isInFoldableLayout = false private var isInFoldableLayout = false
private var emulationStarted = false
private lateinit var gpuModel: String private lateinit var gpuModel: String
private lateinit var fwVersion: String private lateinit var fwVersion: String
private var intentGame: Game? = null
private var isCustomSettingsIntent = false
private var perfStatsRunnable: Runnable? = null
private var socRunnable: Runnable? = null
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
if (context is EmulationActivity) { if (context is EmulationActivity) {
@ -125,9 +136,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
updateOrientation() updateOrientation()
val intentUri: Uri? = requireActivity().intent.data val intent = requireActivity().intent
var intentGame: Game? = null val intentUri: Uri? = intent.data
if (intentUri != null) { intentGame = null
isCustomSettingsIntent = false
if (intent.action == CustomSettingsHandler.CUSTOM_CONFIG_ACTION) {
handleEmuReadyIntent(intent)
return
} else if (intentUri != null) {
intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) { intentGame = if (Game.extensions.contains(FileUtil.getExtension(intentUri))) {
GameHelper.getGame(requireActivity().intent.data!!, false) GameHelper.getGame(requireActivity().intent.data!!, false)
} else { } else {
@ -135,13 +152,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
try { finishGameSetup()
game = if (args.game != null) {
args.game!!
} else {
intentGame!!
} }
} catch (e: NullPointerException) {
/**
* Complete the game setup process (extracted for async custom settings handling)
*/
private fun finishGameSetup() {
try {
val gameToUse = args.game ?: intentGame
if (gameToUse == null) {
Log.error("[EmulationFragment] No game found in arguments or intent")
Toast.makeText( Toast.makeText(
requireContext(), requireContext(),
R.string.no_game_present, R.string.no_game_present,
@ -151,22 +173,288 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return return
} }
// Always load custom settings when launching a game from an intent game = gameToUse
if (args.custom || intentGame != null) {
SettingsFile.loadCustomConfig(game) } catch (e: Exception) {
NativeConfig.unloadPerGameConfig() Log.error("[EmulationFragment] Error during game setup: ${e.message}")
} else { Toast.makeText(
NativeConfig.reloadGlobalConfig() requireContext(),
"Setup error: ${e.message?.take(30) ?: "Unknown"}",
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
return
} }
// Install the selected driver asynchronously as the game starts try {
driverViewModel.onLaunchGame() when {
// Game launched via intent (check for existing custom config)
intentGame != null -> {
game?.let { gameInstance ->
val customConfigFile = SettingsFile.getCustomSettingsFile(gameInstance)
if (customConfigFile.exists()) {
Log.info("[EmulationFragment] Found existing custom settings for ${gameInstance.title}, loading them")
SettingsFile.loadCustomConfig(gameInstance)
} else {
Log.info("[EmulationFragment] No custom settings found for ${gameInstance.title}, using global settings")
NativeConfig.reloadGlobalConfig()
}
} ?: run {
Log.info("[EmulationFragment] No game available, using global settings")
NativeConfig.reloadGlobalConfig()
}
}
// So this fragment doesn't restart on configuration changes; i.e. rotation. // Normal game launch from arguments
retainInstance = true else -> {
emulationState = EmulationState(game.path) { val shouldUseCustom = game?.let { it == args.game && args.custom } ?: false
if (shouldUseCustom) {
SettingsFile.loadCustomConfig(game!!)
Log.info("[EmulationFragment] Loading custom settings for ${game!!.title}")
} else {
Log.info("[EmulationFragment] Using global settings")
NativeConfig.reloadGlobalConfig()
}
}
}
} catch (e: Exception) {
Log.error("[EmulationFragment] Error loading configuration: ${e.message}")
Log.info("[EmulationFragment] Falling back to global settings")
try {
NativeConfig.reloadGlobalConfig()
} catch (fallbackException: Exception) {
Log.error("[EmulationFragment] Critical error: could not load global config: ${fallbackException.message}")
throw fallbackException
}
}
emulationState = EmulationState(game!!.path) {
return@EmulationState driverViewModel.isInteractionAllowed.value return@EmulationState driverViewModel.isInteractionAllowed.value
} }
}
/**
* Handle EmuReady intent for launching games with or without custom settings
*/
private fun handleEmuReadyIntent(intent: Intent) {
val titleId = intent.getStringExtra(CustomSettingsHandler.EXTRA_TITLE_ID)
val customSettings = intent.getStringExtra(CustomSettingsHandler.EXTRA_CUSTOM_SETTINGS)
if (titleId != null) {
Log.info("[EmulationFragment] Received EmuReady intent for title: $titleId")
lifecycleScope.launch {
try {
Toast.makeText(
requireContext(),
getString(R.string.searching_for_game),
Toast.LENGTH_SHORT
).show()
val foundGame = CustomSettingsHandler.findGameByTitleId(
titleId,
requireContext()
)
if (foundGame == null) {
Log.error("[EmulationFragment] Game not found for title ID: $titleId")
Toast.makeText(
requireContext(),
getString(R.string.game_not_found_for_title_id, titleId),
Toast.LENGTH_LONG
).show()
requireActivity().finish()
return@launch
}
val shouldLaunch = showLaunchConfirmationDialog(
foundGame.title,
customSettings != null
)
if (!shouldLaunch) {
Log.info("[EmulationFragment] User cancelled EmuReady launch")
requireActivity().finish()
return@launch
}
if (customSettings != null) {
intentGame = CustomSettingsHandler.applyCustomSettingsWithDriverCheck(
titleId,
customSettings,
requireContext(),
requireActivity(),
driverViewModel
)
if (intentGame == null) {
Log.error(
"[EmulationFragment] Custom settings processing failed for title ID: $titleId"
)
Toast.makeText(
requireContext(),
getString(R.string.custom_settings_failed_title),
Toast.LENGTH_SHORT
).show()
val launchWithDefault = askUserToLaunchWithDefaultSettings(
foundGame.title,
getString(R.string.custom_settings_failure_reasons)
)
if (launchWithDefault) {
Log.info(
"[EmulationFragment] User chose to launch with default settings"
)
Toast.makeText(
requireContext(),
getString(R.string.launch_with_default_settings),
Toast.LENGTH_SHORT
).show()
intentGame = foundGame
isCustomSettingsIntent = false
} else {
Log.info(
"[EmulationFragment] User cancelled launch after custom settings failure"
)
Toast.makeText(
requireContext(),
getString(R.string.launch_cancelled),
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
return@launch
}
} else {
isCustomSettingsIntent = true
}
} else {
Log.info("[EmulationFragment] Launching game with default settings")
val customConfigFile = SettingsFile.getCustomSettingsFile(foundGame)
if (customConfigFile.exists()) {
Log.info("[EmulationFragment] Found existing custom settings for ${foundGame.title}, loading them")
SettingsFile.loadCustomConfig(foundGame)
} else {
Log.info("[EmulationFragment] No custom settings found for ${foundGame.title}, using global settings")
}
Toast.makeText(
requireContext(),
getString(R.string.launching_game, foundGame.title),
Toast.LENGTH_SHORT
).show()
intentGame = foundGame
isCustomSettingsIntent = false
}
if (intentGame != null) {
withContext(Dispatchers.Main) {
try {
finishGameSetup()
Log.info("[EmulationFragment] Game setup complete for intent launch")
if (_binding != null) {
// Hide loading indicator immediately for intent launches
binding.loadingIndicator.visibility = View.GONE
binding.surfaceEmulation.visibility = View.VISIBLE
completeViewSetup()
// For intent launches, check if surface is ready and start emulation
binding.root.post {
if (binding.surfaceEmulation.holder.surface?.isValid == true && !emulationStarted) {
emulationStarted = true
emulationState.newSurface(binding.surfaceEmulation.holder.surface)
}
}
}
} catch (e: Exception) {
Log.error("[EmulationFragment] Error in finishGameSetup: ${e.message}")
requireActivity().finish()
return@withContext
}
}
} else {
Log.error("[EmulationFragment] No valid game found after processing intent")
Toast.makeText(
requireContext(),
getString(R.string.failed_to_initialize_game),
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
}
} catch (e: Exception) {
Log.error("[EmulationFragment] Error processing EmuReady intent: ${e.message}")
Toast.makeText(
requireContext(),
"Error: ${e.message?.take(50) ?: "Unknown error"}",
Toast.LENGTH_LONG
).show()
requireActivity().finish()
}
}
} else {
Log.error("[EmulationFragment] EmuReady intent missing title_id")
Toast.makeText(
requireContext(),
"Invalid request: missing title ID",
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
}
}
/**
* Show confirmation dialog for EmuReady game launches
*/
private suspend fun showLaunchConfirmationDialog(gameTitle: String, hasCustomSettings: Boolean): Boolean {
return suspendCoroutine { continuation ->
requireActivity().runOnUiThread {
val message = if (hasCustomSettings) {
getString(
R.string.custom_intent_launch_message_with_settings,
gameTitle
)
} else {
getString(R.string.custom_intent_launch_message, gameTitle)
}
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.custom_intent_launch_title))
.setMessage(message)
.setPositiveButton(getString(R.string.launch)) { _, _ ->
continuation.resume(true)
}
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
continuation.resume(false)
}
.setCancelable(false)
.show()
}
}
}
/**
* Ask user if they want to launch with default settings when custom settings fail
*/
private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean {
return suspendCoroutine { continuation ->
requireActivity().runOnUiThread {
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.custom_settings_failed_title))
.setMessage(
getString(R.string.custom_settings_failed_message, gameTitle, errorMessage)
)
.setPositiveButton(getString(R.string.launch_with_default_settings)) { _, _ ->
continuation.resume(true)
}
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
continuation.resume(false)
}
.setCancelable(false)
.show()
}
}
} }
/** /**
@ -187,6 +475,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return return
} }
if (game == null) {
Log.warning("[EmulationFragment] Game not yet initialized in onViewCreated - will be set up by async intent handler")
return
}
completeViewSetup()
}
private fun completeViewSetup() {
if (_binding == null || game == null) {
return
}
Log.info("[EmulationFragment] Starting view setup for game: ${game?.title}")
gpuModel = GpuDriverHelper.getGpuModel().toString() gpuModel = GpuDriverHelper.getGpuModel().toString()
fwVersion = NativeLibrary.firmwareVersion() fwVersion = NativeLibrary.firmwareVersion()
@ -223,10 +525,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
}) })
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title) updateGameTitle()
titleView.text = game.title
}
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val lockMode = IntSetting.LOCK_DRAWER.getInt() val lockMode = IntSetting.LOCK_DRAWER.getInt()
@ -293,13 +593,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true true
} }
R.id.menu_multiplayer -> { R.id.menu_multiplayer -> {
emulationActivity?.displayMultiplayerDialog() emulationActivity?.displayMultiplayerDialog()
true true
} }
R.id.menu_controls -> { R.id.menu_controls -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity( val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null, null,
@ -368,8 +666,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
) )
GameIconUtils.loadGameIcon(game, binding.loadingImage) GameIconUtils.loadGameIcon(game!!, binding.loadingImage)
binding.loadingTitle.text = game.title binding.loadingTitle.text = game!!.title
binding.loadingTitle.isSelected = true binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true binding.loadingText.isSelected = true
@ -408,7 +706,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.updateSurface() emulationState.updateSurface()
// Setup overlays
updateShowStatsOverlay() updateShowStatsOverlay()
updateSocOverlay() updateSocOverlay()
@ -418,7 +715,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val cpuBackendLabel = findViewById<TextView>(R.id.cpu_backend) val cpuBackendLabel = findViewById<TextView>(R.id.cpu_backend)
val vendorLabel = findViewById<TextView>(R.id.gpu_vendor) val vendorLabel = findViewById<TextView>(R.id.gpu_vendor)
titleView.text = game.title titleView.text = game?.title ?: ""
cpuBackendLabel.text = NativeLibrary.getCpuBackend() cpuBackendLabel.text = NativeLibrary.getCpuBackend()
vendorLabel.text = NativeLibrary.getGpuDriver() vendorLabel.text = NativeLibrary.getGpuDriver()
} }
@ -456,16 +753,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
ViewUtils.showView(binding.loadingIndicator) ViewUtils.showView(binding.loadingIndicator)
} }
} }
emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
if (it && emulationViewModel.programChanged.value != -1) {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
if (socUpdater != null) { emulationViewModel.emulationStopped.collect(viewLifecycleOwner) { stopped ->
socUpdateHandler.removeCallbacks(socUpdater!!) if (stopped && emulationViewModel.programChanged.value != -1) {
perfStatsRunnable?.let { runnable ->
perfStatsUpdateHandler.removeCallbacks(
runnable
)
} }
socRunnable?.let { runnable -> socUpdateHandler.removeCallbacks(runnable) }
emulationState.changeProgram(emulationViewModel.programChanged.value) emulationState.changeProgram(emulationViewModel.programChanged.value)
emulationViewModel.setProgramChanged(-1) emulationViewModel.setProgramChanged(-1)
emulationViewModel.setEmulationStopped(false) emulationViewModel.setEmulationStopped(false)
@ -473,10 +769,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
if (it) startEmulation() if (it && !NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
startEmulation()
} }
} }
driverViewModel.onLaunchGame()
}
private fun startEmulation(programIndex: Int = 0) { private fun startEmulation(programIndex: Int = 0) {
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
if (!DirectoryInitialization.areDirectoriesReady) { if (!DirectoryInitialization.areDirectoriesReady) {
@ -518,6 +818,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
private fun updateGameTitle() {
game?.let {
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title)
titleView.text = it.title
}
}
}
override fun onPause() { override fun onPause() {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause() emulationState.pause()
@ -634,7 +943,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val batteryTemp = getBatteryTemperature() val batteryTemp = getBatteryTemperature()
when (IntSetting.BAT_TEMPERATURE_UNIT.getInt(needsGlobal)) { when (IntSetting.BAT_TEMPERATURE_UNIT.getInt(needsGlobal)) {
0 -> sb.append(String.format("%.1f°C", batteryTemp)) 0 -> sb.append(String.format("%.1f°C", batteryTemp))
1 -> sb.append(String.format("%.1f°F", celsiusToFahrenheit(batteryTemp))) 1 -> sb.append(
String.format(
"%.1f°F",
celsiusToFahrenheit(batteryTemp)
)
)
} }
} }
@ -643,8 +957,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val battery: BatteryManager = val battery: BatteryManager =
requireContext().getSystemService(Context.BATTERY_SERVICE) as BatteryManager requireContext().getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val batteryIntent = requireContext().registerReceiver(null, val batteryIntent = requireContext().registerReceiver(
IntentFilter(Intent.ACTION_BATTERY_CHANGED)) null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
val capacity = battery.getIntProperty(BATTERY_PROPERTY_CAPACITY) val capacity = battery.getIntProperty(BATTERY_PROPERTY_CAPACITY)
val nowUAmps = battery.getIntProperty(BATTERY_PROPERTY_CURRENT_NOW) val nowUAmps = battery.getIntProperty(BATTERY_PROPERTY_CURRENT_NOW)
@ -671,20 +987,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
if (BooleanSetting.PERF_OVERLAY_BACKGROUND.getBoolean(needsGlobal)) { if (BooleanSetting.PERF_OVERLAY_BACKGROUND.getBoolean(needsGlobal)) {
binding.showStatsOverlayText.setBackgroundResource(R.color.yuzu_transparent_black) binding.showStatsOverlayText.setBackgroundResource(
R.color.yuzu_transparent_black
)
} else { } else {
binding.showStatsOverlayText.setBackgroundResource(0) binding.showStatsOverlayText.setBackgroundResource(0)
} }
binding.showStatsOverlayText.text = sb.toString() binding.showStatsOverlayText.text = sb.toString()
} }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) perfStatsUpdateHandler.postDelayed(perfStatsRunnable!!, 800)
} }
perfStatsUpdateHandler.post(perfStatsUpdater!!) perfStatsRunnable = Runnable { perfStatsUpdater?.invoke() }
perfStatsUpdateHandler.post(perfStatsRunnable!!)
} else { } else {
if (perfStatsUpdater != null) { perfStatsRunnable?.let { perfStatsUpdateHandler.removeCallbacks(it) }
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
} }
} }
@ -767,46 +1084,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
) { ) {
sb.setLength(0) sb.setLength(0)
if (BooleanSetting.SHOW_DEVICE_MODEL.getBoolean(NativeConfig.isPerGameConfigLoaded())) { if (BooleanSetting.SHOW_DEVICE_MODEL.getBoolean(
NativeConfig.isPerGameConfigLoaded()
)
) {
sb.append(Build.MODEL) sb.append(Build.MODEL)
} }
if (BooleanSetting.SHOW_GPU_MODEL.getBoolean(NativeConfig.isPerGameConfigLoaded())) { if (BooleanSetting.SHOW_GPU_MODEL.getBoolean(
NativeConfig.isPerGameConfigLoaded()
)
) {
if (sb.isNotEmpty()) sb.append(" | ") if (sb.isNotEmpty()) sb.append(" | ")
sb.append(gpuModel) sb.append(gpuModel)
} }
if (Build.VERSION.SDK_INT >= 31) { if (Build.VERSION.SDK_INT >= 31) {
if (BooleanSetting.SHOW_SOC_MODEL.getBoolean(NativeConfig.isPerGameConfigLoaded())) { if (BooleanSetting.SHOW_SOC_MODEL.getBoolean(
NativeConfig.isPerGameConfigLoaded()
)
) {
if (sb.isNotEmpty()) sb.append(" | ") if (sb.isNotEmpty()) sb.append(" | ")
sb.append(Build.SOC_MODEL) sb.append(Build.SOC_MODEL)
} }
} }
if (BooleanSetting.SHOW_FW_VERSION.getBoolean(NativeConfig.isPerGameConfigLoaded())) { if (BooleanSetting.SHOW_FW_VERSION.getBoolean(
NativeConfig.isPerGameConfigLoaded()
)
) {
if (sb.isNotEmpty()) sb.append(" | ") if (sb.isNotEmpty()) sb.append(" | ")
sb.append(fwVersion) sb.append(fwVersion)
} }
binding.showSocOverlayText.text = sb.toString() binding.showSocOverlayText.text = sb.toString()
if (BooleanSetting.SOC_OVERLAY_BACKGROUND.getBoolean(NativeConfig.isPerGameConfigLoaded())) { if (BooleanSetting.SOC_OVERLAY_BACKGROUND.getBoolean(
binding.showSocOverlayText.setBackgroundResource(R.color.yuzu_transparent_black) NativeConfig.isPerGameConfigLoaded()
)
) {
binding.showSocOverlayText.setBackgroundResource(
R.color.yuzu_transparent_black
)
} else { } else {
binding.showSocOverlayText.setBackgroundResource(0) binding.showSocOverlayText.setBackgroundResource(0)
} }
} }
socUpdateHandler.postDelayed(socUpdater!!, 1000) socUpdateHandler.postDelayed(socRunnable!!, 1000)
} }
socUpdateHandler.post(socUpdater!!) socRunnable = Runnable { socUpdater?.invoke() }
socUpdateHandler.post(socRunnable!!)
} else { } else {
if (socUpdater != null) { socRunnable?.let { socUpdateHandler.removeCallbacks(it) }
socUpdateHandler.removeCallbacks(socUpdater!!)
} }
} }
}
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() { private fun updateOrientation() {
@ -919,11 +1251,34 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height) Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height)
if (!emulationStarted) {
emulationStarted = true
// For intent launches, wait for driver initialization to complete
if (isCustomSettingsIntent || intentGame != null) {
if (!driverViewModel.isInteractionAllowed.value) {
Log.info("[EmulationFragment] Intent launch: waiting for driver initialization")
// Driver is still initializing, wait for it
lifecycleScope.launch {
driverViewModel.isInteractionAllowed.collect { allowed ->
if (allowed && holder.surface.isValid) {
emulationState.newSurface(holder.surface) emulationState.newSurface(holder.surface)
} }
}
}
return
}
}
emulationState.newSurface(holder.surface)
} else {
emulationState.newSurface(holder.surface)
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) { override fun surfaceDestroyed(holder: SurfaceHolder) {
emulationState.clearSurface() emulationState.clearSurface()
emulationStarted = false
} }
private fun showOverlayOptions() { private fun showOverlayOptions() {
@ -1096,22 +1451,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
inputScaleSlider.apply { inputScaleSlider.apply {
valueTo = 150F valueTo = 150F
value = IntSetting.OVERLAY_SCALE.getInt().toFloat() value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
addOnChangeListener( addOnChangeListener { _, value, _ ->
Slider.OnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%" inputScaleValue.text = "${value.toInt()}%"
setControlScale(value.toInt()) setControlScale(value.toInt())
} }
)
} }
inputOpacitySlider.apply { inputOpacitySlider.apply {
valueTo = 100F valueTo = 100F
value = IntSetting.OVERLAY_OPACITY.getInt().toFloat() value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
addOnChangeListener( addOnChangeListener { _, value, _ ->
Slider.OnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%" inputOpacityValue.text = "${value.toInt()}%"
setControlOpacity(value.toInt()) setControlOpacity(value.toInt())
} }
)
} }
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
@ -1147,7 +1498,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
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
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) { if (v.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
left = cutInsets.left left = cutInsets.left
} else { } else {
right = cutInsets.right right = cutInsets.right
@ -1168,7 +1519,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
lateinit var emulationThread: Thread lateinit var emulationThread: Thread
init { init {
// Starting state is stopped.
state = State.STOPPED state = State.STOPPED
} }
@ -1176,7 +1526,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val isStopped: Boolean val isStopped: Boolean
get() = state == State.STOPPED get() = state == State.STOPPED
// Getters for the current state
@get:Synchronized @get:Synchronized
val isPaused: Boolean val isPaused: Boolean
get() = state == State.PAUSED get() = state == State.PAUSED
@ -1196,7 +1545,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
// State changing methods
@Synchronized @Synchronized
fun pause() { fun pause() {
if (state != State.PAUSED) { if (state != State.PAUSED) {

View file

@ -27,7 +27,6 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections 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
@ -35,15 +34,14 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.DriverViewModel import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeSetting import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
class HomeSettingsFragment : Fragment() { class HomeSettingsFragment : Fragment() {
private var _binding: FragmentHomeSettingsBinding? = null private var _binding: FragmentHomeSettingsBinding? = null
@ -112,7 +110,7 @@ class HomeSettingsFragment : Fragment() {
.actionHomeSettingsFragmentToDriverManagerFragment(null) .actionHomeSettingsFragmentToDriverManagerFragment(null)
binding.root.findNavController().navigate(action) binding.root.findNavController().navigate(action)
}, },
{true}, { true },
R.string.custom_driver_not_supported, R.string.custom_driver_not_supported,
R.string.custom_driver_not_supported_description, R.string.custom_driver_not_supported_description,
driverViewModel.selectedDriverTitle driverViewModel.selectedDriverTitle
@ -125,7 +123,7 @@ class HomeSettingsFragment : Fragment() {
R.drawable.ic_two_users, R.drawable.ic_two_users,
{ {
mainActivity.displayMultiplayerDialog() mainActivity.displayMultiplayerDialog()
}, }
) )
) )
add( add(
@ -254,6 +252,8 @@ class HomeSettingsFragment : Fragment() {
viewLifecycleOwner, viewLifecycleOwner,
optionsList optionsList
) )
val spacing = resources.getDimensionPixelSize(R.dimen.spacing_small)
addItemDecoration(SpacingItemDecoration(spacing))
} }
setInsets() setInsets()
@ -403,7 +403,7 @@ class HomeSettingsFragment : Fragment() {
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
binding.scrollViewSettings.updatePadding( binding.scrollViewSettings.updatePadding(
top = barInsets.top, top = barInsets.top
) )
binding.homeSettingsList.updatePadding( binding.homeSettingsList.updatePadding(

View file

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2025 Eden Emulator Project // SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments package org.yuzu.yuzu_emu.fragments
import android.os.Bundle import android.os.Bundle

View file

@ -78,7 +78,6 @@ class SetupFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity mainActivity = requireActivity() as MainActivity
requireActivity().onBackPressedDispatcher.addCallback( requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner, viewLifecycleOwner,
object : OnBackPressedCallback(true) { object : OnBackPressedCallback(true) {

View file

@ -22,7 +22,11 @@ class MidScreenSwipeRefreshLayout @JvmOverloads constructor(
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
startX = ev.x startX = ev.x
val width = width val width = width
val center_fraction = resources.getFraction(R.fraction.carousel_midscreenswipe_width_fraction, 1, 1).coerceIn(0f, 1f) val center_fraction = resources.getFraction(
R.fraction.carousel_midscreenswipe_width_fraction,
1,
1
).coerceIn(0f, 1f)
val leftBound = ((1 - center_fraction) / 2) * width val leftBound = ((1 - center_fraction) / 2) * width
val rightBound = leftBound + (width * center_fraction) val rightBound = leftBound + (width * center_fraction)
allowRefresh = startX >= leftBound && startX <= rightBound allowRefresh = startX >= leftBound && startX <= rightBound

View file

@ -27,7 +27,7 @@ object NetDataValidators {
fun roomVisibility(s: String, context: Context): Boolean { fun roomVisibility(s: String, context: Context): Boolean {
if (s != context.getString(R.string.multiplayer_public_visibility)) { if (s != context.getString(R.string.multiplayer_public_visibility)) {
return true; return true
} }
return token() return token()

View file

@ -70,7 +70,6 @@ object NetPlayManager {
val gameName: String val gameName: String
) )
private var messageListener: ((Int, String) -> Unit)? = null private var messageListener: ((Int, String) -> Unit)? = null
private var adapterRefreshListener: ((Int, String) -> Unit)? = null private var adapterRefreshListener: ((Int, String) -> Unit)? = null
@ -199,7 +198,6 @@ object NetPlayManager {
} }
} }
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
if (!isChatOpen) { if (!isChatOpen) {
// TODO(alekpop, crueter): Improve this, potentially a drawer at the top? // TODO(alekpop, crueter): Improve this, potentially a drawer at the top?
@ -207,7 +205,6 @@ object NetPlayManager {
} }
} }
messageListener?.invoke(type, msg) messageListener?.invoke(type, msg)
adapterRefreshListener?.invoke(type, msg) adapterRefreshListener?.invoke(type, msg)
} }
@ -218,19 +215,29 @@ object NetPlayManager {
NetPlayStatus.LOST_CONNECTION -> context.getString(R.string.multiplayer_lost_connection) NetPlayStatus.LOST_CONNECTION -> context.getString(R.string.multiplayer_lost_connection)
NetPlayStatus.NAME_COLLISION -> context.getString(R.string.multiplayer_name_collision) NetPlayStatus.NAME_COLLISION -> context.getString(R.string.multiplayer_name_collision)
NetPlayStatus.MAC_COLLISION -> context.getString(R.string.multiplayer_mac_collision) NetPlayStatus.MAC_COLLISION -> context.getString(R.string.multiplayer_mac_collision)
NetPlayStatus.CONSOLE_ID_COLLISION -> context.getString(R.string.multiplayer_console_id_collision) NetPlayStatus.CONSOLE_ID_COLLISION -> context.getString(
R.string.multiplayer_console_id_collision
)
NetPlayStatus.WRONG_VERSION -> context.getString(R.string.multiplayer_wrong_version) NetPlayStatus.WRONG_VERSION -> context.getString(R.string.multiplayer_wrong_version)
NetPlayStatus.WRONG_PASSWORD -> context.getString(R.string.multiplayer_wrong_password) NetPlayStatus.WRONG_PASSWORD -> context.getString(R.string.multiplayer_wrong_password)
NetPlayStatus.COULD_NOT_CONNECT -> context.getString(R.string.multiplayer_could_not_connect) NetPlayStatus.COULD_NOT_CONNECT -> context.getString(
R.string.multiplayer_could_not_connect
)
NetPlayStatus.ROOM_IS_FULL -> context.getString(R.string.multiplayer_room_is_full) NetPlayStatus.ROOM_IS_FULL -> context.getString(R.string.multiplayer_room_is_full)
NetPlayStatus.HOST_BANNED -> context.getString(R.string.multiplayer_host_banned) NetPlayStatus.HOST_BANNED -> context.getString(R.string.multiplayer_host_banned)
NetPlayStatus.PERMISSION_DENIED -> context.getString(R.string.multiplayer_permission_denied) NetPlayStatus.PERMISSION_DENIED -> context.getString(
R.string.multiplayer_permission_denied
)
NetPlayStatus.NO_SUCH_USER -> context.getString(R.string.multiplayer_no_such_user) NetPlayStatus.NO_SUCH_USER -> context.getString(R.string.multiplayer_no_such_user)
NetPlayStatus.ALREADY_IN_ROOM -> context.getString(R.string.multiplayer_already_in_room) NetPlayStatus.ALREADY_IN_ROOM -> context.getString(R.string.multiplayer_already_in_room)
NetPlayStatus.CREATE_ROOM_ERROR -> context.getString(R.string.multiplayer_create_room_error) NetPlayStatus.CREATE_ROOM_ERROR -> context.getString(
R.string.multiplayer_create_room_error
)
NetPlayStatus.HOST_KICKED -> context.getString(R.string.multiplayer_host_kicked) NetPlayStatus.HOST_KICKED -> context.getString(R.string.multiplayer_host_kicked)
NetPlayStatus.UNKNOWN_ERROR -> context.getString(R.string.multiplayer_unknown_error) NetPlayStatus.UNKNOWN_ERROR -> context.getString(R.string.multiplayer_unknown_error)
NetPlayStatus.ROOM_UNINITIALIZED -> context.getString(R.string.multiplayer_room_uninitialized) NetPlayStatus.ROOM_UNINITIALIZED -> context.getString(
R.string.multiplayer_room_uninitialized
)
NetPlayStatus.ROOM_IDLE -> context.getString(R.string.multiplayer_room_idle) NetPlayStatus.ROOM_IDLE -> context.getString(R.string.multiplayer_room_idle)
NetPlayStatus.ROOM_JOINING -> context.getString(R.string.multiplayer_room_joining) NetPlayStatus.ROOM_JOINING -> context.getString(R.string.multiplayer_room_joining)
NetPlayStatus.ROOM_JOINED -> context.getString(R.string.multiplayer_room_joined) NetPlayStatus.ROOM_JOINED -> context.getString(R.string.multiplayer_room_joined)
@ -247,7 +254,9 @@ object NetPlayManager {
msg msg
) )
NetPlayStatus.ADDRESS_UNBANNED -> context.getString(R.string.multiplayer_address_unbanned) NetPlayStatus.ADDRESS_UNBANNED -> context.getString(
R.string.multiplayer_address_unbanned
)
NetPlayStatus.CHAT_MESSAGE -> msg NetPlayStatus.CHAT_MESSAGE -> msg
else -> "" else -> ""
} }

View file

@ -7,20 +7,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.ImageButton
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -111,7 +104,7 @@ class GamesFragment : Fragment() {
} }
gameAdapter = GameAdapter( gameAdapter = GameAdapter(
requireActivity() as AppCompatActivity, requireActivity() as AppCompatActivity
) )
applyGridGamesBinding() applyGridGamesBinding()
@ -238,7 +231,9 @@ class GamesFragment : Fragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (getCurrentViewType() == GameAdapter.VIEW_TYPE_CAROUSEL) { if (getCurrentViewType() == GameAdapter.VIEW_TYPE_CAROUSEL) {
(binding.gridGames as? CarouselRecyclerView)?.restoreScrollState(gamesViewModel.lastScrollPosition) (binding.gridGames as? CarouselRecyclerView)?.restoreScrollState(
gamesViewModel.lastScrollPosition
)
} }
} }
@ -389,7 +384,9 @@ class GamesFragment : Fragment() {
val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault()) val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault())
if (searchTerm.isEmpty()) { if (searchTerm.isEmpty()) {
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(filteredList) ((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(
filteredList
)
gamesViewModel.setFilteredGames(filteredList) gamesViewModel.setFilteredGames(filteredList)
return return
} }
@ -464,7 +461,9 @@ class GamesFragment : Fragment() {
// Always set margin as original + insets // Always set margin as original + insets
mlpHeader.leftMargin = (originalHeaderLeftMargin ?: 0) + leftInset mlpHeader.leftMargin = (originalHeaderLeftMargin ?: 0) + leftInset
mlpHeader.rightMargin = (originalHeaderRightMargin ?: 0) + rightInset mlpHeader.rightMargin = (originalHeaderRightMargin ?: 0) + rightInset
mlpHeader.topMargin = (originalHeaderTopMargin ?: 0) + topInset + resources.getDimensionPixelSize(R.dimen.spacing_med) mlpHeader.topMargin = (originalHeaderTopMargin ?: 0) + topInset + resources.getDimensionPixelSize(
R.dimen.spacing_med
)
binding.header.layoutParams = mlpHeader binding.header.layoutParams = mlpHeader
binding.noticeText.updatePadding(bottom = spacingNavigation) binding.noticeText.updatePadding(bottom = spacingNavigation)

View file

@ -6,8 +6,6 @@ package org.yuzu.yuzu_emu.ui.main
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.provider.OpenableColumns
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import android.view.WindowManager import android.view.WindowManager
@ -49,7 +47,6 @@ import java.io.BufferedOutputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toFile
class MainActivity : AppCompatActivity(), ThemeProvider { class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@ -69,7 +66,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private var checkedFirmware = false private var checkedFirmware = false
private val requestBluetoothPermissionsLauncher = private val requestBluetoothPermissionsLauncher =
registerForActivityResult(androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions()) { permissions -> registerForActivityResult(
androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val granted = permissions.entries.all { it.value } val granted = permissions.entries.all { it.value }
if (granted) { if (granted) {
// Permissions were granted. // Permissions were granted.
@ -111,10 +110,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
checkAndRequestBluetoothPermissions() checkAndRequestBluetoothPermissions()
if (savedInstanceState != null) { if (savedInstanceState != null) {
@ -151,16 +148,20 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
binding.statusBarShade.setBackgroundColor( binding.statusBarShade.setBackgroundColor(
ThemeHelper.getColorWithOpacity( ThemeHelper.getColorWithOpacity(
MaterialColors.getColor( MaterialColors.getColor(
binding.root, com.google.android.material.R.attr.colorSurface binding.root,
), ThemeHelper.SYSTEM_BAR_ALPHA com.google.android.material.R.attr.colorSurface
),
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(
binding.root, com.google.android.material.R.attr.colorSurface binding.root,
), ThemeHelper.SYSTEM_BAR_ALPHA com.google.android.material.R.attr.colorSurface
),
ThemeHelper.SYSTEM_BAR_ALPHA
) )
) )
} }
@ -171,7 +172,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) } homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) }
homeViewModel.contentToInstall.collect( homeViewModel.contentToInstall.collect(
this, resetState = { homeViewModel.setContentToInstall(null) }) { this,
resetState = { homeViewModel.setContentToInstall(null) }
) {
if (it != null) { if (it != null) {
installContent(it) installContent(it)
} }
@ -181,7 +184,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
homeViewModel.checkFirmware.collect( homeViewModel.checkFirmware.collect(
this, resetState = { homeViewModel.setCheckFirmware(false) }) { this,
resetState = { homeViewModel.setCheckFirmware(false) }
) {
if (it) checkFirmware() if (it) checkFirmware()
} }
@ -204,7 +209,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() { PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() {
putBoolean(Settings.PREF_SHOULD_SHOW_PRE_ALPHA_WARNING, false) putBoolean(Settings.PREF_SHOULD_SHOW_PRE_ALPHA_WARNING, false)
} }
}).show(supportFragmentManager, MessageDialogFragment.TAG) }
).show(supportFragmentManager, MessageDialogFragment.TAG)
} }
} }
@ -225,7 +231,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
private fun checkFirmware() { private fun checkFirmware() {
val resultCode: Int = NativeLibrary.verifyFirmware() val resultCode: Int = NativeLibrary.verifyFirmware()
if (resultCode == 0) return; if (resultCode == 0) return
val resultString: String = val resultString: String =
resources.getStringArray(R.array.verifyFirmwareResults)[resultCode] resources.getStringArray(R.array.verifyFirmwareResults)[resultCode]
@ -313,14 +319,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun processGamesDir(result: Uri, calledFromGameFragment: Boolean = false) { fun processGamesDir(result: Uri, calledFromGameFragment: Boolean = false) {
contentResolver.takePersistableUriPermission( contentResolver.takePersistableUriPermission(
result, Intent.FLAG_GRANT_READ_URI_PERMISSION result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
) )
val uriString = result.toString() val uriString = result.toString()
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString } val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
if (folder != null) { if (folder != null) {
Toast.makeText( Toast.makeText(
applicationContext, R.string.folder_already_added, Toast.LENGTH_SHORT applicationContext,
R.string.folder_already_added,
Toast.LENGTH_SHORT
).show() ).show()
return return
} }
@ -343,16 +352,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
fun processKey(result: Uri, extension: String = "keys") { fun processKey(result: Uri, extension: String = "keys") {
contentResolver.takePersistableUriPermission( contentResolver.takePersistableUriPermission(
result, Intent.FLAG_GRANT_READ_URI_PERMISSION result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
) )
val resultCode: Int = NativeLibrary.installKeys(result.toString(), extension); val resultCode: Int = NativeLibrary.installKeys(result.toString(), extension)
if (resultCode == 0) { if (resultCode == 0) {
// TODO(crueter): It may be worth it to switch some of these Toasts to snackbars, // TODO(crueter): It may be worth it to switch some of these Toasts to snackbars,
// since most of it is foreground-only anyways. // since most of it is foreground-only anyways.
Toast.makeText( Toast.makeText(
applicationContext, R.string.keys_install_success, Toast.LENGTH_SHORT applicationContext,
R.string.keys_install_success,
Toast.LENGTH_SHORT
).show() ).show()
gamesViewModel.reloadGames(true) gamesViewModel.reloadGames(true)
@ -384,12 +396,15 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val cacheFirmwareDir = File("${cacheDir.path}/registered/") val cacheFirmwareDir = File("${cacheDir.path}/registered/")
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this, R.string.firmware_installing this,
R.string.firmware_installing
) { progressCallback, _ -> ) { progressCallback, _ ->
var messageToShow: Any var messageToShow: Any
try { try {
FileUtil.unzipToInternalStorage( FileUtil.unzipToInternalStorage(
result.toString(), cacheFirmwareDir, progressCallback result.toString(),
cacheFirmwareDir,
progressCallback
) )
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1 val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2 val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
@ -423,7 +438,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val firmwarePath = val firmwarePath =
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/") File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this, R.string.firmware_uninstalling this,
R.string.firmware_uninstalling
) { progressCallback, _ -> ) { progressCallback, _ ->
var messageToShow: Any var messageToShow: Any
try { try {
@ -459,12 +475,15 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this@MainActivity, R.string.verifying_content, false this@MainActivity,
R.string.verifying_content,
false
) { _, _ -> ) { _, _ ->
var updatesMatchProgram = true var updatesMatchProgram = true
for (document in documents) { for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram( val valid = NativeLibrary.doesUpdateMatchProgram(
addonViewModel.game!!.programId, document.toString() addonViewModel.game!!.programId,
document.toString()
) )
if (!valid) { if (!valid) {
updatesMatchProgram = false updatesMatchProgram = false
@ -480,14 +499,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
titleId = R.string.content_install_notice, titleId = R.string.content_install_notice,
descriptionId = R.string.content_install_notice_description, descriptionId = R.string.content_install_notice_description,
positiveAction = { homeViewModel.setContentToInstall(documents) }, positiveAction = { homeViewModel.setContentToInstall(documents) },
negativeAction = {}) negativeAction = {}
)
} }
}.show(supportFragmentManager, ProgressDialogFragment.TAG) }.show(supportFragmentManager, ProgressDialogFragment.TAG)
} }
private fun installContent(documents: List<Uri>) { private fun installContent(documents: List<Uri>) {
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this@MainActivity, R.string.installing_game_content this@MainActivity,
R.string.installing_game_content
) { progressCallback, messageCallback -> ) { progressCallback, messageCallback ->
var installSuccess = 0 var installSuccess = 0
var installOverwrite = 0 var installOverwrite = 0
@ -495,11 +516,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
var error = 0 var error = 0
documents.forEach { documents.forEach {
messageCallback.invoke(FileUtil.getFilename(it)) messageCallback.invoke(FileUtil.getFilename(it))
when (InstallResult.from( when (
InstallResult.from(
NativeLibrary.installFileToNand( NativeLibrary.installFileToNand(
it.toString(), progressCallback it.toString(),
progressCallback
) )
)) { )
) {
InstallResult.Success -> { InstallResult.Success -> {
installSuccess += 1 installSuccess += 1
} }
@ -525,7 +549,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (installSuccess > 0) { if (installSuccess > 0) {
installResult.append( installResult.append(
getString( getString(
R.string.install_game_content_success_install, installSuccess R.string.install_game_content_success_install,
installSuccess
) )
) )
installResult.append(separator) installResult.append(separator)
@ -533,7 +558,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (installOverwrite > 0) { if (installOverwrite > 0) {
installResult.append( installResult.append(
getString( getString(
R.string.install_game_content_success_overwrite, installOverwrite R.string.install_game_content_success_overwrite,
installOverwrite
) )
) )
installResult.append(separator) installResult.append(separator)
@ -543,7 +569,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
installResult.append(separator) installResult.append(separator)
installResult.append( installResult.append(
getString( getString(
R.string.install_game_content_failed_count, errorTotal R.string.install_game_content_failed_count,
errorTotal
) )
) )
installResult.append(separator) installResult.append(separator)
@ -584,7 +611,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this, R.string.exporting_user_data, true this,
R.string.exporting_user_data,
true
) { progressCallback, _ -> ) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage( val zipResult = FileUtil.zipFromInternalStorage(
File(DirectoryInitialization.userDirectory!!), File(DirectoryInitialization.userDirectory!!),
@ -608,7 +637,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
} }
ProgressDialogFragment.newInstance( ProgressDialogFragment.newInstance(
this, R.string.importing_user_data this,
R.string.importing_user_data
) { progressCallback, _ -> ) { progressCallback, _ ->
val checkStream = val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result))) ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))

View file

@ -0,0 +1,314 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.widget.Toast
import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import android.net.Uri
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
object CustomSettingsHandler {
const val CUSTOM_CONFIG_ACTION = "dev.eden.eden_emulator.LAUNCH_WITH_CUSTOM_CONFIG"
const val EXTRA_TITLE_ID = "title_id"
const val EXTRA_CUSTOM_SETTINGS = "custom_settings"
/**
* Apply custom settings from a string instead of loading from file
* @param titleId The game title ID (16-digit hex string)
* @param customSettings The complete INI file content as string
* @param context Application context
* @return Game object created from title ID, or null if not found
*/
fun applyCustomSettings(titleId: String, customSettings: String, context: Context): Game? {
// For synchronous calls without driver checking
Log.info("[CustomSettingsHandler] Applying custom settings for title ID: $titleId")
// Find the game by title ID
val game = findGameByTitleId(titleId, context)
if (game == null) {
Log.error("[CustomSettingsHandler] Game not found for title ID: $titleId")
return null
}
// Check if config already exists - this should be handled by the caller
val configFile = getConfigFile(game)
if (configFile.exists()) {
Log.warning("[CustomSettingsHandler] Config file already exists for game: ${game.title}")
}
// Write the config file
if (!writeConfigFile(game, customSettings)) {
Log.error("[CustomSettingsHandler] Failed to write config file")
return null
}
// Initialize per-game config
try {
val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.programId, fileName)
Log.info("[CustomSettingsHandler] Successfully applied custom settings")
return game
} catch (e: Exception) {
Log.error("[CustomSettingsHandler] Failed to apply custom settings: ${e.message}")
return null
}
}
/**
* Apply custom settings with automatic driver checking and installation
* @param titleId The game title ID (16-digit hex string)
* @param customSettings The complete INI file content as string
* @param context Application context
* @param activity Fragment activity for driver installation dialogs (optional)
* @param driverViewModel DriverViewModel for driver management (optional)
* @return Game object created from title ID, or null if not found
*/
suspend fun applyCustomSettingsWithDriverCheck(
titleId: String,
customSettings: String,
context: Context,
activity: FragmentActivity?,
driverViewModel: DriverViewModel?
): Game? {
Log.info("[CustomSettingsHandler] Applying custom settings for title ID: $titleId")
// Find the game by title ID
val game = findGameByTitleId(titleId, context)
if (game == null) {
Log.error("[CustomSettingsHandler] Game not found for title ID: $titleId")
// This will be handled by the caller to show appropriate error message
return null
}
// Check if config already exists
val configFile = getConfigFile(game)
if (configFile.exists() && activity != null) {
Log.info(
"[CustomSettingsHandler] Config file already exists, asking user for confirmation"
)
Toast.makeText(
activity,
activity.getString(R.string.config_exists_prompt),
Toast.LENGTH_SHORT
).show()
val shouldOverwrite = askUserToOverwriteConfig(activity, game.title)
if (!shouldOverwrite) {
Log.info("[CustomSettingsHandler] User chose not to overwrite existing config")
Toast.makeText(
activity,
activity.getString(R.string.overwrite_cancelled),
Toast.LENGTH_SHORT
).show()
return null
}
}
// Check for driver requirements if activity and driverViewModel are provided
if (activity != null && driverViewModel != null) {
val driverPath = extractDriverPath(customSettings)
if (driverPath != null) {
Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath")
// Check if driver exists in the driver storage
val driverFile = File(driverPath)
if (!driverFile.exists()) {
Log.error("[CustomSettingsHandler] Required driver not found: $driverPath")
Toast.makeText(
activity,
activity.getString(
R.string.custom_settings_failed_message,
game.title,
activity.getString(R.string.driver_not_found, driverFile.name)
),
Toast.LENGTH_LONG
).show()
// Don't write config if driver is missing
return null
}
// Verify it's a valid driver
val metadata = GpuDriverHelper.getMetadataFromZip(driverFile)
if (metadata.name == null) {
Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath")
Toast.makeText(
activity,
activity.getString(
R.string.custom_settings_failed_message,
game.title,
activity.getString(R.string.invalid_driver_file, driverFile.name)
),
Toast.LENGTH_LONG
).show()
return null
}
Log.info("[CustomSettingsHandler] Driver verified: ${metadata.name}")
}
}
// Only write the config file after all checks pass
if (!writeConfigFile(game, customSettings)) {
Log.error("[CustomSettingsHandler] Failed to write config file")
activity?.let {
Toast.makeText(
it,
it.getString(R.string.config_write_failed),
Toast.LENGTH_SHORT
).show()
}
return null
}
// Initialize per-game config
try {
SettingsFile.loadCustomConfig(game)
Log.info("[CustomSettingsHandler] Successfully applied custom settings")
activity?.let {
Toast.makeText(
it,
it.getString(R.string.custom_settings_applied),
Toast.LENGTH_SHORT
).show()
}
return game
} catch (e: Exception) {
Log.error("[CustomSettingsHandler] Failed to apply custom settings: ${e.message}")
activity?.let {
Toast.makeText(
it,
it.getString(R.string.config_apply_failed),
Toast.LENGTH_SHORT
).show()
}
return null
}
}
/**
* Find a game by its title ID in the user's game library
*/
fun findGameByTitleId(titleId: String, context: Context): Game? {
Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId")
// Convert hex title ID to decimal for comparison with programId
val programIdDecimal = try {
titleId.toLong(16).toString()
} catch (e: NumberFormatException) {
Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId")
return null
}
// Expected hex format with "0" prefix
val expectedHex = "0${titleId.uppercase()}"
// First check cached games for fast lookup
GameHelper.cachedGameList.find { game ->
game.programId == programIdDecimal ||
game.programIdHex.equals(expectedHex, ignoreCase = true)
}?.let { foundGame ->
Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}")
return foundGame
}
// If not in cache, perform full game library scan
Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...")
val allGames = GameHelper.getGames()
val foundGame = allGames.find { game ->
game.programId == programIdDecimal ||
game.programIdHex.equals(expectedHex, ignoreCase = true)
}
if (foundGame != null) {
Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}")
} else {
Log.warning("[CustomSettingsHandler] No game found for title ID: $titleId")
}
return foundGame
}
/**
* Get the config file path for a game
*/
private fun getConfigFile(game: Game): File {
return SettingsFile.getCustomSettingsFile(game)
}
/**
* Get the title ID config file path
*/
private fun getTitleIdConfigFile(titleId: String): File {
val configDir = File(DirectoryInitialization.userDirectory, "config/custom")
return File(configDir, "$titleId.ini")
}
/**
* Write the config file with the custom settings
*/
private fun writeConfigFile(game: Game, customSettings: String): Boolean {
return try {
val configFile = getConfigFile(game)
val configDir = configFile.parentFile
if (configDir != null && !configDir.exists()) {
configDir.mkdirs()
}
configFile.writeText(customSettings)
Log.info("[CustomSettingsHandler] Wrote config file: ${configFile.absolutePath}")
true
} catch (e: Exception) {
Log.error("[CustomSettingsHandler] Failed to write config file: ${e.message}")
false
}
}
/**
* Ask user if they want to overwrite existing configuration
*/
private suspend fun askUserToOverwriteConfig(activity: FragmentActivity, gameTitle: String): Boolean {
return suspendCoroutine { continuation ->
activity.runOnUiThread {
MaterialAlertDialogBuilder(activity)
.setTitle(activity.getString(R.string.config_already_exists_title))
.setMessage(
activity.getString(R.string.config_already_exists_message, gameTitle)
)
.setPositiveButton(activity.getString(R.string.overwrite)) { _, _ ->
continuation.resume(true)
}
.setNegativeButton(activity.getString(R.string.cancel)) { _, _ ->
continuation.resume(false)
}
.setCancelable(false)
.show()
}
}
}
/**
* Extract driver path from custom settings INI content
*/
private fun extractDriverPath(customSettings: String): String? {
val lines = customSettings.lines()
var inGpuDriverSection = false
for (line in lines) {
val trimmed = line.trim()
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
inGpuDriverSection = trimmed == "[GpuDriver]"
continue
}
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
return trimmed.substringAfter("driver_path=")
}
}
return null
}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@ -197,7 +200,9 @@ object FileUtil {
*/ */
fun getFilename(uri: Uri): String { fun getFilename(uri: Uri): String {
if (uri.scheme == "file") { if (uri.scheme == "file") {
return uri.lastPathSegment?.takeIf { it.isNotEmpty() } ?: throw IOException("Invalid file URI: $uri") return uri.lastPathSegment?.takeIf { it.isNotEmpty() } ?: throw IOException(
"Invalid file URI: $uri"
)
} }
val resolver = YuzuApplication.appContext.contentResolver val resolver = YuzuApplication.appContext.contentResolver

View file

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.os.Handler import android.os.Handler
@ -17,7 +16,6 @@ object PowerStateUpdater {
private var isStarted = false private var isStarted = false
fun start() { fun start() {
if (isStarted) { if (isStarted) {
return return
} }

View file

@ -13,7 +13,6 @@ object PowerStateUtils {
@JvmStatic @JvmStatic
fun getBatteryInfo(context: Context?): IntArray { fun getBatteryInfo(context: Context?): IntArray {
if (context == null) { if (context == null) {
return intArrayOf(0, 0, 0) // Percentage, IsCharging, HasBattery return intArrayOf(0, 0, 0) // Percentage, IsCharging, HasBattery
} }

View file

@ -23,10 +23,12 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
object ThemeHelper { object ThemeHelper {
const val SYSTEM_BAR_ALPHA = 0.9f const val SYSTEM_BAR_ALPHA = 0.9f
// Listener that detects if the theme keys are being changed from the setting menu and recreates the activity // Listener that detects if the theme keys are being changed from the setting menu and recreates the activity
private var listener: SharedPreferences.OnSharedPreferenceChangeListener? = null private var listener: SharedPreferences.OnSharedPreferenceChangeListener? = null
private val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) private val preferences = PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
fun setTheme(activity: AppCompatActivity) { fun setTheme(activity: AppCompatActivity) {
setThemeMode(activity) setThemeMode(activity)
@ -52,6 +54,7 @@ object ThemeHelper {
private fun getSelectedStaticThemeColor(): Int { private fun getSelectedStaticThemeColor(): Int {
val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0) val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
val themes = arrayOf( val themes = arrayOf(
R.style.Theme_Eden_Main,
R.style.Theme_Yuzu_Main_Violet, R.style.Theme_Yuzu_Main_Violet,
R.style.Theme_Yuzu_Main_Blue, R.style.Theme_Yuzu_Main_Blue,
R.style.Theme_Yuzu_Main_Cyan, R.style.Theme_Yuzu_Main_Cyan,
@ -120,7 +123,11 @@ object ThemeHelper {
fun ThemeChangeListener(activity: AppCompatActivity) { fun ThemeChangeListener(activity: AppCompatActivity) {
listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
val relevantKeys = listOf(Settings.PREF_STATIC_THEME_COLOR, Settings.PREF_THEME_MODE, Settings.PREF_BLACK_BACKGROUNDS) val relevantKeys = listOf(
Settings.PREF_STATIC_THEME_COLOR,
Settings.PREF_THEME_MODE,
Settings.PREF_BLACK_BACKGROUNDS
)
if (key in relevantKeys) { if (key in relevantKeys) {
activity.recreate() activity.recreate()
} }

View file

@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.ui package org.yuzu.yuzu_emu.ui
import android.content.Context import android.content.Context
@ -53,7 +56,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
var flingMultiplier: Float = 1f var flingMultiplier: Float = 1f
public var pendingScrollAfterReload: Boolean = false var pendingScrollAfterReload: Boolean = false
var useCustomDrawingOrder: Boolean = false var useCustomDrawingOrder: Boolean = false
set(value) { set(value) {
@ -76,7 +79,11 @@ class CarouselRecyclerView @JvmOverloads constructor(
private fun getLayoutManagerCenter(layoutManager: RecyclerView.LayoutManager): Int { private fun getLayoutManagerCenter(layoutManager: RecyclerView.LayoutManager): Int {
return if (layoutManager is LinearLayoutManager) { return if (layoutManager is LinearLayoutManager) {
calculateCenter(layoutManager.width, layoutManager.paddingStart, layoutManager.paddingEnd) calculateCenter(
layoutManager.width,
layoutManager.paddingStart,
layoutManager.paddingEnd
)
} else { } else {
width / 2 width / 2
} }
@ -121,13 +128,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
fun shapingFunction(x: Float, option: Int = 0): Float { fun shapingFunction(x: Float, option: Int = 0): Float {
return when (option) { return when (option) {
0 -> 1f //Off 0 -> 1f // Off
1 -> 1f - x //linear descending 1 -> 1f - x // linear descending
2 -> (1f - x) * (1f - x) //Ease out 2 -> (1f - x) * (1f - x) // Ease out
3 -> if (x < 0.05f) 1f else (1f-x) * 0.8f 3 -> if (x < 0.05f) 1f else (1f - x) * 0.8f
4 -> kotlin.math.cos(x * Math.PI).toFloat() //Cosine 4 -> kotlin.math.cos(x * Math.PI).toFloat() // Cosine
5 -> kotlin.math.cos( (1.5f * x).coerceIn(0f, 1f) * Math.PI).toFloat() //Cosine 1.5x trimmed 5 -> kotlin.math.cos((1.5f * x).coerceIn(0f, 1f) * Math.PI).toFloat() // Cosine 1.5x trimmed
else -> 1f //Default to Off else -> 1f // Default to Off
} }
} }
@ -143,20 +150,36 @@ class CarouselRecyclerView @JvmOverloads constructor(
val center = getRecyclerViewCenter() val center = getRecyclerViewCenter()
val distance = abs(getChildDistanceToCenter(child)) val distance = abs(getChildDistanceToCenter(child))
val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1) val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1)
val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(0f, 1f) val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn(
0f,
1f
)
val shapeInput = (distance / center).coerceIn(0f, 1f) val shapeInput = (distance / center).coerceIn(0f, 1f)
val internalShapeSetting = resources.getInteger(R.integer.carousel_cards_scaling_shape) val internalShapeSetting = resources.getInteger(R.integer.carousel_cards_scaling_shape)
val scalingShapeSetting = preferences.getInt(CAROUSEL_CARDS_SCALING_SHAPE, internalShapeSetting) val scalingShapeSetting = preferences.getInt(
CAROUSEL_CARDS_SCALING_SHAPE,
internalShapeSetting
)
val shapedScaling = shapingFunction(shapeInput, scalingShapeSetting) val shapedScaling = shapingFunction(shapeInput, scalingShapeSetting)
val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f) val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
val maxDistance = width / 2f val maxDistance = width / 2f
val alphaInput = (distance / maxDistance).coerceIn(0f, 1f) val alphaInput = (distance / maxDistance).coerceIn(0f, 1f)
val internalBordersAlpha = resources.getFraction(R.fraction.carousel_bordercards_alpha, 1, 1) val internalBordersAlpha = resources.getFraction(
val borderAlpha = preferences.getFloat(CAROUSEL_BORDERCARDS_ALPHA, internalBordersAlpha).coerceIn(0f, 1f) R.fraction.carousel_bordercards_alpha,
1,
1
)
val borderAlpha = preferences.getFloat(CAROUSEL_BORDERCARDS_ALPHA, internalBordersAlpha).coerceIn(
0f,
1f
)
val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape) val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape)
val alphaShapeSetting = preferences.getInt(CAROUSEL_CARDS_ALPHA_SHAPE, internalAlphaShapeSetting) val alphaShapeSetting = preferences.getInt(
CAROUSEL_CARDS_ALPHA_SHAPE,
internalAlphaShapeSetting
)
val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting) val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting)
val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f) val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
@ -185,16 +208,33 @@ class CarouselRecyclerView @JvmOverloads constructor(
val insets = rootWindowInsets val insets = rootWindowInsets
val bottomInset = insets?.getInsets(android.view.WindowInsets.Type.systemBars())?.bottom ?: 0 val bottomInset = insets?.getInsets(android.view.WindowInsets.Type.systemBars())?.bottom ?: 0
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1) val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(0f, 1f) val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
0f,
1f
)
val cardSize = (userFactor * (height - bottomInset)).toInt() val cardSize = (userFactor * (height - bottomInset)).toInt()
gameAdapter?.setCardSize(cardSize) gameAdapter?.setCardSize(cardSize)
val internalOverlapFactor = resources.getFraction(R.fraction.carousel_overlap_factor, 1, 1) val internalOverlapFactor = resources.getFraction(
overlapFactor = preferences.getFloat(CAROUSEL_OVERLAP_FACTOR, internalOverlapFactor).coerceIn(0f, 1f) R.fraction.carousel_overlap_factor,
1,
1
)
overlapFactor = preferences.getFloat(CAROUSEL_OVERLAP_FACTOR, internalOverlapFactor).coerceIn(
0f,
1f
)
overlapPx = (cardSize * overlapFactor).toInt() overlapPx = (cardSize * overlapFactor).toInt()
val internalFlingMultiplier = resources.getFraction(R.fraction.carousel_fling_multiplier, 1, 1) val internalFlingMultiplier = resources.getFraction(
flingMultiplier = preferences.getFloat(CAROUSEL_FLING_MULTIPLIER, internalFlingMultiplier).coerceIn(1f, 5f) R.fraction.carousel_fling_multiplier,
1,
1
)
flingMultiplier = preferences.getFloat(
CAROUSEL_FLING_MULTIPLIER,
internalFlingMultiplier
).coerceIn(1f, 5f)
gameAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { gameAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
@ -290,20 +330,28 @@ class CarouselRecyclerView @JvmOverloads constructor(
View.FOCUS_LEFT -> { View.FOCUS_LEFT -> {
if (position > 0) { if (position > 0) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val repeatDetected = (now - lastFocusSearchTime) < resources.getInteger(R.integer.carousel_focus_search_repeat_threshold_ms) val repeatDetected = (now - lastFocusSearchTime) < resources.getInteger(
R.integer.carousel_focus_search_repeat_threshold_ms
)
lastFocusSearchTime = now lastFocusSearchTime = now
if (!repeatDetected) { //ensures the first run if (!repeatDetected) { // ensures the first run
val offset = focused.width - overlapPx val offset = focused.width - overlapPx
smoothScrollBy(-offset, 0) smoothScrollBy(-offset, 0)
} }
findViewHolderForAdapterPosition(position - 1)?.itemView ?: super.focusSearch(focused, direction) findViewHolderForAdapterPosition(position - 1)?.itemView ?: super.focusSearch(
focused,
direction
)
} else { } else {
focused focused
} }
} }
View.FOCUS_RIGHT -> { View.FOCUS_RIGHT -> {
if (position < itemCount - 1) { if (position < itemCount - 1) {
findViewHolderForAdapterPosition(position + 1)?.itemView ?: super.focusSearch(focused, direction) findViewHolderForAdapterPosition(position + 1)?.itemView ?: super.focusSearch(
focused,
direction
)
} else { } else {
focused focused
} }
@ -341,7 +389,10 @@ class CarouselRecyclerView @JvmOverloads constructor(
inner class OverlappingDecoration(private val overlap: Int) : ItemDecoration() { inner class OverlappingDecoration(private val overlap: Int) : ItemDecoration() {
override fun getItemOffsets( override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView, state: State outRect: Rect,
view: View,
parent: RecyclerView,
state: State
) { ) {
val position = parent.getChildAdapterPosition(view) val position = parent.getChildAdapterPosition(view)
if (position > 0) { if (position > 0) {
@ -378,12 +429,17 @@ class CarouselRecyclerView @JvmOverloads constructor(
return layoutManager.findViewByPosition(getClosestChildPosition()) return layoutManager.findViewByPosition(getClosestChildPosition())
} }
//NEEDED: fixes ghost movement when snapping, but breaks inertial scrolling // NEEDED: fixes ghost movement when snapping, but breaks inertial scrolling
override fun calculateDistanceToFinalSnap( override fun calculateDistanceToFinalSnap(
layoutManager: RecyclerView.LayoutManager, layoutManager: RecyclerView.LayoutManager,
targetView: View targetView: View
): IntArray? { ): IntArray? {
if (layoutManager !is LinearLayoutManager) return super.calculateDistanceToFinalSnap(layoutManager, targetView) if (layoutManager !is LinearLayoutManager) {
return super.calculateDistanceToFinalSnap(
layoutManager,
targetView
)
}
val out = IntArray(2) val out = IntArray(2)
out[0] = getChildDistanceToCenter(targetView).toInt() out[0] = getChildDistanceToCenter(targetView).toInt()
out[1] = 0 out[1] = 0
@ -399,10 +455,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
if (layoutManager !is LinearLayoutManager) return RecyclerView.NO_POSITION if (layoutManager !is LinearLayoutManager) return RecyclerView.NO_POSITION
val closestPosition = this@CarouselRecyclerView.getClosestChildPosition() val closestPosition = this@CarouselRecyclerView.getClosestChildPosition()
val internalMaxFling = resources.getInteger(R.integer.carousel_max_fling_count) val internalMaxFling = resources.getInteger(R.integer.carousel_max_fling_count)
val maxFling = preferences.getInt(CAROUSEL_MAX_FLING_COUNT, internalMaxFling).coerceIn(1, 10) val maxFling = preferences.getInt(CAROUSEL_MAX_FLING_COUNT, internalMaxFling).coerceIn(
1,
10
)
val rawFlingCount = if (velocityX == 0) 0 else velocityX / 2000 val rawFlingCount = if (velocityX == 0) 0 else velocityX / 2000
val flingCount = rawFlingCount.coerceIn(-maxFling, maxFling) val flingCount = rawFlingCount.coerceIn(-maxFling, maxFling)
var targetPos = (closestPosition + flingCount).coerceIn(0, layoutManager.itemCount - 1) val targetPos = (closestPosition + flingCount).coerceIn(0, layoutManager.itemCount - 1)
return targetPos return targetPos
} }
} }

View file

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.views
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import com.google.android.material.card.MaterialCardView
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.features.settings.model.Settings
import androidx.preference.PreferenceManager
class GradientBorderCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MaterialCardView(context, attrs, defStyleAttr) {
private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeWidth = 6f
}
private val borderPath = Path()
private val borderRect = RectF()
private var showGradientBorder = false
private var isEdenTheme = false
init {
setWillNotDraw(false)
updateThemeState()
}
private fun updateThemeState() {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val themeIndex = try {
prefs.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
} catch (e: Exception) {
0 // Default to Eden theme if error
}
isEdenTheme = themeIndex == 0
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// Update border style based on theme
if (isEdenTheme) {
// Gradient for Eden theme
borderPaint.shader = LinearGradient(
0f, 0f,
w.toFloat(), h.toFloat(),
context.getColor(R.color.eden_border_gradient_start),
context.getColor(R.color.eden_border_gradient_end),
Shader.TileMode.CLAMP
)
} else {
// Solid color for other themes
borderPaint.shader = null
val typedValue = android.util.TypedValue()
context.theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue, true)
borderPaint.color = typedValue.data
}
// Update border rect with padding for stroke
val halfStroke = borderPaint.strokeWidth / 2
borderRect.set(
halfStroke,
halfStroke,
w - halfStroke,
h - halfStroke
)
// Update path with rounded corners
borderPath.reset()
borderPath.addRoundRect(
borderRect,
radius,
radius,
Path.Direction.CW
)
}
override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
showGradientBorder = gainFocus
invalidate()
}
override fun setSelected(selected: Boolean) {
super.setSelected(selected)
showGradientBorder = selected
invalidate()
}
override fun setPressed(pressed: Boolean) {
super.setPressed(pressed)
if (pressed) {
showGradientBorder = true
invalidate()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (showGradientBorder && !isPressed) {
canvas.drawPath(borderPath, borderPaint)
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
updateThemeState()
requestLayout()
}
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/eden_background" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<gradient
android:angle="135"
android:centerColor="@color/eden_transparent"
android:centerX="0.5"
android:centerY="0.5"
android:endColor="@color/eden_primary_transparent"
android:gradientRadius="100%"
android:startColor="@color/eden_secondary_transparent"
android:type="radial" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<gradient
android:angle="135"
android:startColor="@color/eden_primary_variant"
android:endColor="@color/eden_accent_purple"
android:type="linear" />
<corners android:radius="12dp" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<gradient
android:angle="135"
android:startColor="@color/eden_primary"
android:endColor="@color/eden_primary_variant"
android:type="linear" />
<corners android:radius="12dp" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/eden_card_background" />
<corners android:radius="16dp" />
<stroke
android:width="1dp"
android:color="@color/eden_border" />
</shape>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/eden_card_background_elevated" />
<corners android:radius="16dp" />
<stroke
android:width="1dp"
android:color="@color/eden_border_light" />
</shape>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Pressed state with gradient border -->
<item android:state_pressed="true" android:drawable="@drawable/eden_gradient_border" />
<!-- Selected state with gradient border -->
<item android:state_selected="true" android:drawable="@drawable/eden_gradient_border" />
<!-- Focused state with gradient border -->
<item android:state_focused="true" android:drawable="@drawable/eden_gradient_border" />
<!-- Default state with elevated background -->
<item android:drawable="@drawable/eden_card_elevated_background" />
</selector>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<gradient
android:angle="45"
android:startColor="@color/eden_border_gradient_start"
android:endColor="@color/eden_border_gradient_end"
android:type="linear" />
<corners android:radius="24dp" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="rectangle">
<solid android:color="@color/eden_card_background" />
<corners android:radius="23dp" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<gradient
android:angle="45"
android:startColor="@color/eden_border_gradient_start"
android:endColor="@color/eden_border_gradient_end"
android:type="linear" />
<corners android:radius="16dp" />
</shape>
</item>
<item
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:top="2dp">
<shape android:shape="rectangle">
<solid android:color="@color/eden_card_background" />
<corners android:radius="14dp" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Pressed state with gradient highlight -->
<item android:state_pressed="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<gradient
android:angle="45"
android:startColor="@color/eden_border_gradient_start"
android:endColor="@color/eden_border_gradient_end"
android:type="linear" />
<corners android:radius="12dp" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="rectangle">
<solid android:color="@color/eden_card_background" />
<corners android:radius="11dp" />
</shape>
</item>
</layer-list>
</item>
<!-- Selected/focused state -->
<item android:state_selected="true">
<layer-list>
<item>
<shape android:shape="rectangle">
<gradient
android:angle="45"
android:startColor="@color/eden_border_gradient_start"
android:endColor="@color/eden_border_gradient_end"
android:type="linear" />
<corners android:radius="12dp" />
</shape>
</item>
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="rectangle">
<solid android:color="@color/eden_card_background" />
<corners android:radius="11dp" />
</shape>
</item>
</layer-list>
</item>
<!-- Default state -->
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorSurface" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="?attr/colorOutline" />
</shape>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorSurface" />
<corners android:radius="16dp" />
<stroke
android:width="1dp"
android:color="?attr/colorOutline" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorSurface" />
<corners android:radius="24dp" />
</shape>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Pressed state -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="?attr/colorSurfaceVariant" />
<corners android:radius="12dp" />
</shape>
</item>
<!-- Selected/focused state -->
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="?attr/colorSurfaceVariant" />
<corners android:radius="12dp" />
</shape>
</item>
<!-- Default state -->
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
</shape>
</item>
</selector>

View file

@ -1,27 +1,30 @@
<com.google.android.material.card.MaterialCardView <org.yuzu.yuzu_emu.views.GradientBorderCardView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_game_carousel" android:id="@+id/card_game_carousel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
app:cardCornerRadius="8dp" app:cardCornerRadius="16dp"
app:cardElevation="4dp" app:cardElevation="0dp"
app:cardPreventCornerOverlap="true"
android:clipChildren="true"
android:layout_margin="0dp" android:layout_margin="0dp"
app:strokeColor="@android:color/transparent" app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="0dp" app:strokeWidth="1dp"
android:alpha="0"> app:strokeColor="@color/eden_border">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="4dp"> android:padding="4dp">
<ImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen" android:id="@+id/image_game_screen"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:contentDescription="@string/game_image_desc" android:contentDescription="@string/game_image_desc"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Eden.CarouselImage"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -43,4 +46,4 @@
android:text="Game Title" /> android:text="Game Title" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </org.yuzu.yuzu_emu.views.GradientBorderCardView>

View file

@ -4,7 +4,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:clipChildren="false" android:clipChildren="false"
> >
@ -44,7 +43,10 @@
style="?attr/materialCardViewFilledStyle" style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="24dp"
app:cardBackgroundColor="?attr/colorSurfaceVariant"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
> >
<LinearLayout <LinearLayout
@ -100,6 +102,9 @@
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="21dp"
app:cardBackgroundColor="@color/eden_surface_variant"
app:strokeColor="@color/eden_border"
app:strokeWidth="1dp"
> >
<ImageView <ImageView
@ -123,6 +128,9 @@
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="21dp"
app:cardBackgroundColor="@color/eden_surface_variant"
app:strokeColor="@color/eden_border"
app:strokeWidth="1dp"
> >
<ImageView <ImageView
@ -146,6 +154,9 @@
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="21dp"
app:cardBackgroundColor="@color/eden_surface_variant"
app:strokeColor="@color/eden_border"
app:strokeWidth="1dp"
> >
<ImageView <ImageView
@ -210,9 +221,9 @@
app:icon="@drawable/ic_cartridge_outline" app:icon="@drawable/ic_cartridge_outline"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:textColor="?attr/colorOnPrimaryContainer" android:textColor="?attr/colorOnPrimary"
app:backgroundTint="?attr/colorPrimaryContainer" app:backgroundTint="?attr/colorPrimary"
app:iconTint="?attr/colorOnPrimaryContainer" app:iconTint="?attr/colorOnPrimary"
/> />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,7 +6,7 @@
android:id="@+id/coordinator_main" android:id="@+id/coordinator_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"> android:background="@drawable/eden_background_gradient">
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View file

@ -5,14 +5,16 @@
android:id="@+id/coordinator_about" android:id="@+id/coordinator_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"> >
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_about" android:id="@+id/appbar_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"> android:touchscreenBlocksFocus="false"
android:background="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_about" android:id="@+id/toolbar_about"
@ -37,33 +39,42 @@
android:id="@+id/content_about" android:id="@+id/content_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal"
android:padding="24dp">
<ImageView <ImageView
android:id="@+id/image_logo" android:id="@+id/image_logo"
android:layout_width="200dp" android:layout_width="200dp"
android:layout_height="200dp" android:layout_height="200dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_vertical"
android:padding="20dp" android:layout_marginEnd="32dp"
android:src="@drawable/ic_yuzu_title" /> android:src="@drawable/ic_yuzu_title" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="16dp" android:paddingHorizontal="24dp"
android:paddingVertical="16dp"> android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:text="@string/about" android:text="@string/about"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
@ -71,32 +82,36 @@
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:text="@string/about_app_description" android:text="@string/about_app_description"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_contributors" android:id="@+id/button_contributors"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:layout_marginTop="12dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="16dp" android:paddingHorizontal="24dp"
android:paddingVertical="16dp"> android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:text="@string/contributors" android:text="@string/contributors"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
@ -104,32 +119,36 @@
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:text="@string/contributors_description" android:text="@string/contributors_description"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_licenses" android:id="@+id/button_licenses"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:layout_marginTop="12dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="16dp" android:paddingHorizontal="24dp"
android:paddingVertical="16dp"> android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:text="@string/licenses" android:text="@string/licenses"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
@ -137,32 +156,36 @@
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:text="@string/licenses_description" android:text="@string/licenses_description"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_version_name" android:id="@+id/button_version_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:layout_marginTop="12dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="16dp" android:paddingHorizontal="24dp"
android:paddingVertical="16dp"> android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:text="@string/build" android:text="@string/build"
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
@ -171,59 +194,51 @@
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
tools:text="abc123" /> tools:text="abc123" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp" android:layout_marginTop="24dp"
android:layout_marginTop="12dp" android:gravity="start"
android:layout_marginBottom="16dp"
android:gravity="center_horizontal"
android:orientation="horizontal"> android:orientation="horizontal">
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/button_discord" android:id="@+id/button_discord"
style="?attr/materialIconButtonStyle" style="@style/EdenButton.Secondary"
android:layout_width="0dp" android:layout_width="56dp"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_weight="1" android:layout_marginEnd="12dp"
app:icon="@drawable/ic_discord" app:icon="@drawable/ic_discord"
app:iconGravity="textEnd" app:iconGravity="textStart"
app:iconSize="24dp" app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface" /> app:iconPadding="0dp" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/button_website" android:id="@+id/button_website"
style="?attr/materialIconButtonStyle" style="@style/EdenButton.Secondary"
android:layout_width="0dp" android:layout_width="56dp"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_weight="1" android:layout_marginEnd="12dp"
app:icon="@drawable/ic_website" app:icon="@drawable/ic_website"
app:iconGravity="textEnd" app:iconGravity="textStart"
app:iconSize="24dp" app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface" /> app:iconPadding="0dp" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/button_github" android:id="@+id/button_github"
style="?attr/materialIconButtonStyle" style="@style/EdenButton.Secondary"
android:layout_width="0dp" android:layout_width="56dp"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_weight="1"
app:icon="@drawable/ic_github" app:icon="@drawable/ic_github"
app:iconGravity="textEnd" app:iconGravity="textStart"
app:iconSize="24dp" app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface" /> app:iconPadding="0dp" />
</LinearLayout> </LinearLayout>

View file

@ -23,7 +23,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_next" android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton" style="@style/EdenButton.Primary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/next" android:text="@string/next"
@ -33,7 +33,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_back" android:id="@+id/button_back"
style="@style/Widget.Material3.Button.TextButton" style="@style/EdenButton.Secondary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/back" android:text="@string/back"

View file

@ -78,6 +78,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_action" android:id="@+id/button_action"
style="@style/EdenButton.Primary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"

View file

@ -6,7 +6,7 @@
android:id="@+id/coordinator_main" android:id="@+id/coordinator_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"> android:background="?android:attr/colorBackground">
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View file

@ -2,7 +2,7 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle" style="@style/EdenCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"

View file

@ -1,26 +1,30 @@
<com.google.android.material.card.MaterialCardView <org.yuzu.yuzu_emu.views.GradientBorderCardView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_game_carousel" android:id="@+id/card_game_carousel"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="1dp"
app:strokeColor="@color/eden_border"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
app:cardCornerRadius="8dp" app:cardCornerRadius="16dp"
app:cardElevation="4dp" app:cardPreventCornerOverlap="true"
android:layout_margin="4dp" android:clipChildren="true"
app:strokeColor="@android:color/transparent" android:layout_margin="4dp">
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="8dp"> android:padding="8dp">
<ImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen" android:id="@+id/image_game_screen"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:contentDescription="@string/game_image_desc" android:contentDescription="@string/game_image_desc"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Eden.CarouselImage"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -29,12 +33,14 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title" android:id="@+id/text_game_title"
style="@style/TextAppearance.Material3.TitleMedium" style="@style/SynthwaveText.Body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:requiresFadingEdge="horizontal" android:requiresFadingEdge="horizontal"
android:textAlignment="center" android:textAlignment="center"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@+id/image_game_screen" app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -42,4 +48,4 @@
android:text="Game Title" /> android:text="Game Title" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </org.yuzu.yuzu_emu.views.GradientBorderCardView>

View file

@ -3,22 +3,24 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:focusable="false"
android:focusableInTouchMode="false">
<com.google.android.material.card.MaterialCardView <org.yuzu.yuzu_emu.views.GradientBorderCardView
android:id="@+id/card_game_grid" android:id="@+id/card_game_grid"
style="?attr/materialCardViewElevatedStyle" app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="1dp"
app:strokeColor="@color/eden_border"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:clipToPadding="true" android:clipToPadding="true"
android:focusable="true" android:focusable="true"
android:transitionName="card_game" android:transitionName="card_game"
app:cardCornerRadius="4dp" app:cardCornerRadius="16dp">
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -33,17 +35,19 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium" app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium"
android:scaleType="centerCrop"
tools:src="@drawable/default_icon" /> tools:src="@drawable/default_icon" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title" android:id="@+id/text_game_title"
style="@style/TextAppearance.Material3.TitleMedium" style="@style/SynthwaveText.Body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:requiresFadingEdge="horizontal" android:requiresFadingEdge="horizontal"
android:textAlignment="center" android:textAlignment="center"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/image_game_screen" app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
app:layout_constraintStart_toStartOf="@+id/image_game_screen" app:layout_constraintStart_toStartOf="@+id/image_game_screen"
app:layout_constraintTop_toBottomOf="@+id/image_game_screen" app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
@ -51,6 +55,6 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </org.yuzu.yuzu_emu.views.GradientBorderCardView>
</FrameLayout> </FrameLayout>

View file

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <org.yuzu.yuzu_emu.views.GradientBorderCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_game_list" android:id="@+id/card_game_list"
style="?attr/materialCardViewStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:transitionName="card_game" android:transitionName="card_game"
app:cardCornerRadius="14dp" app:cardCornerRadius="16dp"
app:cardElevation="0dp"> app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -31,7 +31,7 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title" android:id="@+id/text_game_title"
style="@style/TextAppearance.Material3.TitleMedium" style="@style/SynthwaveText.Body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@ -39,6 +39,7 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:singleLine="true" android:singleLine="true"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/text_game_developer" app:layout_constraintBottom_toTopOf="@+id/text_game_developer"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/image_game_screen" app:layout_constraintStart_toEndOf="@+id/image_game_screen"
@ -48,7 +49,7 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_developer" android:id="@+id/text_game_developer"
style="@style/TextAppearance.Material3.BodySmall" style="@style/SynthwaveText.Body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@ -56,6 +57,7 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:singleLine="true" android:singleLine="true"
android:textSize="12sp" android:textSize="12sp"
android:alpha="0.7"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/image_game_screen" app:layout_constraintStart_toEndOf="@+id/image_game_screen"
@ -64,4 +66,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </org.yuzu.yuzu_emu.views.GradientBorderCardView>

View file

@ -2,16 +2,15 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewStyle" style="@style/EdenCard"
android:id="@+id/option_card" android:id="@+id/option_card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" android:layout_marginBottom="16dp"
android:layout_marginHorizontal="12dp" android:layout_marginHorizontal="8dp"
android:background="?attr/colorSurface"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:cardElevation="0dp"> app:cardCornerRadius="16dp">
<LinearLayout <LinearLayout
android:id="@+id/option_layout" android:id="@+id/option_layout"
@ -25,7 +24,7 @@
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
app:tint="?attr/colorOnSurface" /> app:tint="?attr/colorPrimary" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -35,7 +34,7 @@
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium" style="@style/SynthwaveText.Body"
android:id="@+id/option_title" android:id="@+id/option_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -45,17 +44,18 @@
tools:text="@string/install_prod_keys" /> tools:text="@string/install_prod_keys" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodySmall" style="@style/SynthwaveText.Body"
android:id="@+id/option_description" android:id="@+id/option_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textSize="14sp" android:textSize="14sp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:alpha="0.8"
tools:text="@string/install_prod_keys_description" /> tools:text="@string/install_prod_keys_description" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.LabelMedium" style="@style/SynthwaveText.Secondary"
android:id="@+id/option_detail" android:id="@+id/option_detail"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -2,7 +2,7 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle" style="@style/EdenCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"

View file

@ -6,14 +6,15 @@
android:orientation="vertical" android:orientation="vertical"
android:gravity="center" android:gravity="center"
app:strokeWidth="0dp" app:strokeWidth="0dp"
app:cardCornerRadius="24dp"> app:cardCornerRadius="24dp"
android:background="@drawable/theme_dialog_background">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center" android:gravity="center"
android:background="?colorSurface"> android:background="@android:color/transparent">
<View <View
android:layout_width="128dp" android:layout_width="128dp"

View file

@ -5,14 +5,16 @@
android:id="@+id/coordinator_about" android:id="@+id/coordinator_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"> >
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_about" android:id="@+id/appbar_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false"> android:touchscreenBlocksFocus="false"
android:background="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_about" android:id="@+id/toolbar_about"
@ -37,7 +39,8 @@
android:id="@+id/content_about" android:id="@+id/content_about"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical"
android:paddingBottom="24dp">
<ImageView <ImageView
android:id="@+id/image_logo" android:id="@+id/image_logo"
@ -48,89 +51,94 @@
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu_title" /> android:src="@drawable/ic_yuzu_title" />
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
style="@style/EdenCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:cardCornerRadius="16dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:paddingVertical="20dp"
android:paddingHorizontal="16dp" android:paddingHorizontal="20dp"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/SynthwaveText.Header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/about" /> android:text="@string/about" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium" style="@style/SynthwaveText.Body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/about_app_description" /> android:text="@string/about_app_description" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_contributors" android:id="@+id/button_contributors"
style="@style/EdenCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:layout_marginHorizontal="16dp"
android:paddingHorizontal="16dp" android:layout_marginTop="12dp"
android:background="?attr/selectableItemBackground" android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="20dp"
android:paddingHorizontal="20dp"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/SynthwaveText.Header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/contributors" /> android:text="@string/contributors" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium" style="@style/SynthwaveText.Body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/contributors_description" /> android:text="@string/contributors_description" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_licenses" android:id="@+id/button_licenses"
style="@style/EdenCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:layout_marginHorizontal="16dp"
android:paddingHorizontal="16dp" android:layout_marginTop="12dp"
android:background="?attr/selectableItemBackground" android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="20dp"
android:paddingHorizontal="20dp"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/licenses" /> android:text="@string/licenses" />
@ -138,32 +146,37 @@
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/licenses_description" /> android:text="@string/licenses_description" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout
android:id="@+id/button_version_name" android:id="@+id/button_version_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:layout_marginHorizontal="16dp"
android:paddingHorizontal="16dp" android:layout_marginTop="12dp"
android:background="?attr/selectableItemBackground" android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:cardBackgroundColor="?attr/colorSurface"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="20dp"
android:paddingHorizontal="20dp"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium" style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:text="@string/build" /> android:text="@string/build" />
@ -172,59 +185,57 @@
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:textAlignment="viewStart" android:textAlignment="viewStart"
tools:text="abc123" /> tools:text="abc123" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:layout_marginTop="12dp" android:layout_marginTop="24dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:layout_marginHorizontal="40dp"> android:layout_marginHorizontal="40dp">
<Button <com.google.android.material.button.MaterialButton
style="?attr/materialIconButtonStyle" style="@style/EdenButton.Secondary"
android:id="@+id/button_discord" android:id="@+id/button_discord"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginEnd="8dp"
app:icon="@drawable/ic_discord" app:icon="@drawable/ic_discord"
app:iconTint="?attr/colorOnSurface"
app:iconSize="24dp" app:iconSize="24dp"
app:iconGravity="textEnd" /> app:iconGravity="textStart"
app:iconPadding="0dp" />
<Button <com.google.android.material.button.MaterialButton
style="?attr/materialIconButtonStyle" style="@style/EdenButton.Secondary"
android:id="@+id/button_website" android:id="@+id/button_website"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginHorizontal="4dp"
app:icon="@drawable/ic_website" app:icon="@drawable/ic_website"
app:iconTint="?attr/colorOnSurface"
app:iconSize="24dp" app:iconSize="24dp"
app:iconGravity="textEnd" /> app:iconGravity="textStart"
app:iconPadding="0dp" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/button_github" android:id="@+id/button_github"
style="?attr/materialIconButtonStyle" style="@style/EdenButton.Secondary"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="8dp"
app:icon="@drawable/ic_github" app:icon="@drawable/ic_github"
app:iconTint="?attr/colorOnSurface"
app:iconSize="24dp" app:iconSize="24dp"
app:iconGravity="textEnd" /> app:iconGravity="textStart"
app:iconPadding="0dp" />
</LinearLayout> </LinearLayout>

View file

@ -4,7 +4,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"
> >
<LinearLayout <LinearLayout
@ -21,9 +20,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/app_name" android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.Material3.HeadlineLarge" style="@style/SynthwaveText.Title"
android:textSize="27sp" android:textSize="27sp"
android:textStyle="bold"
/> />
<Space <Space
@ -34,10 +32,11 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/view_button" android:id="@+id/view_button"
style="?attr/materialCardViewFilledStyle" style="@style/EdenCard"
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="21dp"
android:padding="8dp"
> >
<ImageView <ImageView
@ -45,7 +44,7 @@
android:layout_height="18dp" android:layout_height="18dp"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@drawable/ic_eye" android:src="@drawable/ic_eye"
app:tint="?attr/colorOnSurfaceVariant" app:tint="?attr/colorSecondary"
/> />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@ -57,10 +56,11 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/filter_button" android:id="@+id/filter_button"
style="?attr/materialCardViewFilledStyle" style="@style/EdenCard"
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="21dp"
android:padding="8dp"
> >
<ImageView <ImageView
@ -80,10 +80,11 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/settings_button" android:id="@+id/settings_button"
style="?attr/materialCardViewFilledStyle" style="@style/EdenCard"
android:layout_width="42dp" android:layout_width="42dp"
android:layout_height="42dp" android:layout_height="42dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="21dp"
android:padding="8dp"
> >
<ImageView <ImageView
@ -91,7 +92,7 @@
android:layout_height="18dp" android:layout_height="18dp"
android:layout_gravity="center" android:layout_gravity="center"
android:src="@drawable/ic_settings" android:src="@drawable/ic_settings"
app:tint="?attr/colorOnSurfaceVariant" app:tint="?attr/colorTertiary"
/> />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@ -111,10 +112,11 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/search_background" android:id="@+id/search_background"
style="?attr/materialCardViewFilledStyle" style="@style/EdenCard"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
app:cardCornerRadius="21dp" app:cardCornerRadius="24dp"
android:padding="4dp"
> >
<LinearLayout <LinearLayout
@ -132,7 +134,7 @@
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:layout_marginEnd="18dp" android:layout_marginEnd="18dp"
android:src="@drawable/ic_search" android:src="@drawable/ic_search"
app:tint="?attr/colorOnSurfaceVariant" app:tint="?attr/colorSecondary"
/> />
<EditText <EditText
@ -144,6 +146,9 @@
android:inputType="text" android:inputType="text"
android:maxLines="1" android:maxLines="1"
android:imeOptions="flagNoFullscreen" android:imeOptions="flagNoFullscreen"
android:textColor="?attr/colorOnBackground"
android:textColorHint="?attr/colorOnSurfaceVariant"
android:fontFamily="monospace"
/> />
</LinearLayout> </LinearLayout>
@ -186,6 +191,7 @@
android:gravity="center" android:gravity="center"
android:padding="@dimen/spacing_large" android:padding="@dimen/spacing_large"
android:text="@string/empty_gamelist" android:text="@string/empty_gamelist"
android:textColor="?attr/colorOnBackground"
android:visibility="gone" android:visibility="gone"
/> />
@ -216,9 +222,9 @@
app:icon="@drawable/ic_cartridge_outline" app:icon="@drawable/ic_cartridge_outline"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:textColor="?attr/colorOnPrimaryContainer" android:textColor="?attr/colorOnPrimary"
app:backgroundTint="?attr/colorPrimaryContainer" app:backgroundTint="?attr/colorPrimary"
app:iconTint="?attr/colorOnPrimaryContainer" app:iconTint="?attr/colorOnPrimary"
/> />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -4,7 +4,6 @@
android:id="@+id/scroll_view_settings" android:id="@+id/scroll_view_settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:scrollbars="vertical" android:scrollbars="vertical"
android:fadeScrollbars="false" android:fadeScrollbars="false"
android:clipToPadding="false" android:clipToPadding="false"
@ -15,21 +14,21 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:background="?attr/colorSurface" android:paddingHorizontal="16dp">
android:paddingHorizontal="8dp">
<ImageView <ImageView
android:id="@+id/logo_image" android:id="@+id/logo_image"
android:layout_width="96dp" android:layout_width="120dp"
android:layout_height="96dp" android:layout_height="120dp"
android:layout_marginVertical="32dp" android:layout_marginVertical="48dp"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:src="@drawable/ic_yuzu_full" /> android:src="@drawable/ic_yuzu_full" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_settings_list" android:id="@+id/home_settings_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:clipToPadding="false" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -20,7 +20,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize" android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:contentScrim="?attr/colorOnSurfaceInverse" app:contentScrim="?attr/colorSurface"
app:scrimVisibleHeightTrigger="100dp"> app:scrimVisibleHeightTrigger="100dp">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar

View file

@ -23,7 +23,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_next" android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton" style="@style/EdenButton.Primary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/next" android:text="@string/next"
@ -33,7 +33,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_back" android:id="@+id/button_back"
style="@style/Widget.Material3.Button.TextButton" style="@style/EdenButton.Secondary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/back" android:text="@string/back"

View file

@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="@drawable/theme_list_item_selector"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center_vertical" android:gravity="center_vertical"

View file

@ -5,7 +5,7 @@
android:id="@+id/setting_body" android:id="@+id/setting_body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="@drawable/theme_list_item_selector"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:gravity="center_vertical" android:gravity="center_vertical"

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground" android:background="@drawable/theme_list_item_selector"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:minHeight="72dp" android:minHeight="72dp"

View file

@ -26,12 +26,10 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_title" android:id="@+id/text_title"
style="@style/TextAppearance.Material3.DisplaySmall" style="@style/SynthwaveText.Header"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:textAlignment="center" android:textAlignment="center"
android:textColor="?attr/colorOnSurface"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/text_description" app:layout_constraintBottom_toTopOf="@+id/text_description"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -41,7 +39,7 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_description" android:id="@+id/text_description"
style="@style/TextAppearance.Material3.TitleLarge" style="@style/SynthwaveText.Body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:textAlignment="center" android:textAlignment="center"
@ -57,7 +55,7 @@
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_confirmation" android:id="@+id/text_confirmation"
style="@style/TextAppearance.Material3.TitleLarge" style="@style/SynthwaveText.Accent"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
@ -66,7 +64,6 @@
android:textSize="30sp" android:textSize="30sp"
android:visibility="invisible" android:visibility="invisible"
android:text="@string/step_complete" android:text="@string/step_complete"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -76,6 +73,7 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_action" android:id="@+id/button_action"
style="@style/EdenButton.Primary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"

View file

@ -155,8 +155,8 @@
<string name="shader_backend">خلفية Shader</string> <string name="shader_backend">خلفية Shader</string>
<string name="shader_backend_description">اختيار طريقة ترجمة Shaders</string> <string name="shader_backend_description">اختيار طريقة ترجمة Shaders</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">محاكاة NVDEC</string> <string name="nvdec_emulation">محاكاة NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">شادەر باکند</string> <string name="shader_backend">شادەر باکند</string>
<string name="shader_backend_description">هەڵبژاردنی ڕێگای پێکهێنانی شادەر</string> <string name="shader_backend_description">هەڵبژاردنی ڕێگای پێکهێنانی شادەر</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">ئیمولەیشنی NVDEC</string> <string name="nvdec_emulation">ئیمولەیشنی NVDEC</string>

View file

@ -152,8 +152,8 @@
<string name="shader_backend">Backend shaderů</string> <string name="shader_backend">Backend shaderů</string>
<string name="shader_backend_description">Způsob kompilace shaderů</string> <string name="shader_backend_description">Způsob kompilace shaderů</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulace NVDEC</string> <string name="nvdec_emulation">Emulace NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Shader-Backend</string> <string name="shader_backend">Shader-Backend</string>
<string name="shader_backend_description">Methode zur Shader-Kompilierung</string> <string name="shader_backend_description">Methode zur Shader-Kompilierung</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC-Emulation</string> <string name="nvdec_emulation">NVDEC-Emulation</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend de shaders</string> <string name="shader_backend">Backend de shaders</string>
<string name="shader_backend_description">Elegir cómo se compilan shaders</string> <string name="shader_backend_description">Elegir cómo se compilan shaders</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulación NVDEC</string> <string name="nvdec_emulation">Emulación NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">بک‌اند شیدر</string> <string name="shader_backend">بک‌اند شیدر</string>
<string name="shader_backend_description">انتخاب روش کامپایل و ترجمه شیدرها</string> <string name="shader_backend_description">انتخاب روش کامپایل و ترجمه شیدرها</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">شبیه‌سازی NVDEC</string> <string name="nvdec_emulation">شبیه‌سازی NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend shader</string> <string name="shader_backend">Backend shader</string>
<string name="shader_backend_description">Méthode de compilation</string> <string name="shader_backend_description">Méthode de compilation</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Émulation NVDEC</string> <string name="nvdec_emulation">Émulation NVDEC</string>

View file

@ -154,8 +154,8 @@
<string name="shader_backend">מנוע שיידרים</string> <string name="shader_backend">מנוע שיידרים</string>
<string name="shader_backend_description">בחר כיצד לקמפל שיידרים</string> <string name="shader_backend_description">בחר כיצד לקמפל שיידרים</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">אמולציית NVDEC</string> <string name="nvdec_emulation">אמולציית NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Shader backend</string> <string name="shader_backend">Shader backend</string>
<string name="shader_backend_description">Shaderek fordításának módja</string> <string name="shader_backend_description">Shaderek fordításának módja</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC emuláció</string> <string name="nvdec_emulation">NVDEC emuláció</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend Shader</string> <string name="shader_backend">Backend Shader</string>
<string name="shader_backend_description">Pilih cara shader dikompilasi dan diterjemahkan untuk GPU Anda.</string> <string name="shader_backend_description">Pilih cara shader dikompilasi dan diterjemahkan untuk GPU Anda.</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulasi NVDEC</string> <string name="nvdec_emulation">Emulasi NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend shader</string> <string name="shader_backend">Backend shader</string>
<string name="shader_backend_description">Scegli come compilare gli shader</string> <string name="shader_backend_description">Scegli come compilare gli shader</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulazione NVDEC</string> <string name="nvdec_emulation">Emulazione NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">シェーダーバックエンド</string> <string name="shader_backend">シェーダーバックエンド</string>
<string name="shader_backend_description">シェーダーのコンパイル方法</string> <string name="shader_backend_description">シェーダーのコンパイル方法</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDECエミュレーション</string> <string name="nvdec_emulation">NVDECエミュレーション</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">셰이더 백엔드</string> <string name="shader_backend">셰이더 백엔드</string>
<string name="shader_backend_description">셰이더 컴파일 방식 선택</string> <string name="shader_backend_description">셰이더 컴파일 방식 선택</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC 에뮬레이션</string> <string name="nvdec_emulation">NVDEC 에뮬레이션</string>
<string name="nvdec_emulation_description">비디오 디코딩 처리 방식 선택</string> <string name="nvdec_emulation_description">비디오 디코딩 처리 방식 선택</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Shader-backend</string> <string name="shader_backend">Shader-backend</string>
<string name="shader_backend_description">Velg hvordan shadere kompileres</string> <string name="shader_backend_description">Velg hvordan shadere kompileres</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC-emulering</string> <string name="nvdec_emulation">NVDEC-emulering</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend shaderów</string> <string name="shader_backend">Backend shaderów</string>
<string name="shader_backend_description">Wybierz metodę kompilacji shaderów.</string> <string name="shader_backend_description">Wybierz metodę kompilacji shaderów.</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulacja NVDEC</string> <string name="nvdec_emulation">Emulacja NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend de shader</string> <string name="shader_backend">Backend de shader</string>
<string name="shader_backend_description">Define como shaders são compilados</string> <string name="shader_backend_description">Define como shaders são compilados</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulação NVDEC</string> <string name="nvdec_emulation">Emulação NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend de Shader</string> <string name="shader_backend">Backend de Shader</string>
<string name="shader_backend_description">Método de compilação de shaders.</string> <string name="shader_backend_description">Método de compilação de shaders.</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulação NVDEC</string> <string name="nvdec_emulation">Emulação NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Шейдерный бэкенд</string> <string name="shader_backend">Шейдерный бэкенд</string>
<string name="shader_backend_description">Метод компиляции шейдеров</string> <string name="shader_backend_description">Метод компиляции шейдеров</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Эмуляция NVDEC</string> <string name="nvdec_emulation">Эмуляция NVDEC</string>

View file

@ -118,8 +118,8 @@
<string name="shader_backend">Схадер Бацкенд</string> <string name="shader_backend">Схадер Бацкенд</string>
<string name="shader_backend_description">Изаберите како се сјеначици саставе и преведете за ваш ГПУ.</string> <string name="shader_backend_description">Изаберите како се сјеначици саставе и преведете за ваш ГПУ.</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">НВДЕЦ Емулација</string> <string name="nvdec_emulation">НВДЕЦ Емулација</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Система обробки шейдерів</string> <string name="shader_backend">Система обробки шейдерів</string>
<string name="shader_backend_description">Спосіб компіляції шейдерів</string> <string name="shader_backend_description">Спосіб компіляції шейдерів</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Емуляція NVDEC</string> <string name="nvdec_emulation">Емуляція NVDEC</string>

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Backend Shader</string> <string name="shader_backend">Backend Shader</string>
<string name="shader_backend_description">Chọn cách biên dịch shader</string> <string name="shader_backend_description">Chọn cách biên dịch shader</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">Giả lập NVDEC</string> <string name="nvdec_emulation">Giả lập NVDEC</string>

View file

@ -152,8 +152,8 @@
<string name="shader_backend">着色器后端</string> <string name="shader_backend">着色器后端</string>
<string name="shader_backend_description">选择着色器编译方式</string> <string name="shader_backend_description">选择着色器编译方式</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC模拟</string> <string name="nvdec_emulation">NVDEC模拟</string>

View file

@ -156,8 +156,8 @@
<string name="shader_backend">著色器後端</string> <string name="shader_backend">著色器後端</string>
<string name="shader_backend_description">選擇著色器的編譯與轉譯方式</string> <string name="shader_backend_description">選擇著色器的編譯與轉譯方式</string>
<string name="shader_backend_glsl">GLSL</string> <string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string> <string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>string> <string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation --> <!-- NVDEC Emulation -->
<string name="nvdec_emulation">NVDEC模擬</string> <string name="nvdec_emulation">NVDEC模擬</string>

View file

@ -389,6 +389,7 @@
</integer-array> </integer-array>
<string-array name="staticThemeNames"> <string-array name="staticThemeNames">
<item>@string/eden_theme</item>
<item>@string/violet</item> <item>@string/violet</item>
<item>@string/blue</item> <item>@string/blue</item>
<item>@string/cyan</item> <item>@string/cyan</item>
@ -409,6 +410,7 @@
<item>6</item> <item>6</item>
<item>7</item> <item>7</item>
<item>8</item> <item>8</item>
<item>9</item>
</integer-array> </integer-array>
<string-array name="anisoEntries"> <string-array name="anisoEntries">

Some files were not shown because too many files have changed in this diff Show more