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

Merged
crueter merged 5 commits from feat/android-emuready-v2 into master 2025-08-03 17:03:54 +02:00
106 changed files with 2223 additions and 686 deletions
Showing only changes of commit f4b0930df2 - Show all commits

View file

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

View file

@ -12,7 +12,7 @@ SPDX-FileCopyrightText: Eden Emulator Project
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.gamepad" 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:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
tools:targetApi="33"
android:enableOnBackInvokedCallback="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" />
<data android:mimeType="application/octet-stream" android:scheme="content"/>
</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" />
</activity>
@ -100,4 +105,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</provider>
</application>
</manifest>
</manifest>

View file

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

View file

@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.activities
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.ParamPackage
import org.yuzu.yuzu_emu.utils.ThemeHelper
import org.yuzu.yuzu_emu.utils.PowerStateUtils
import java.text.NumberFormat
import kotlin.math.roundToInt
@ -204,6 +202,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(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)
InputHandler.updateControllerData()
}
@ -421,7 +425,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
NetPlayManager.addNetPlayMessage(type, msg)
}
private var pictureInPictureReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == actionPlay) {

View file

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

View file

@ -6,10 +6,8 @@ package org.yuzu.yuzu_emu.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.LifecycleOwner
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
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 message: String,
val timestamp: String = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date())
) {
}
)
class ChatDialog(context: Context) : BottomSheetDialog(context) {
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.skipCollapsed = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
behavior.skipCollapsed =
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
handler.post {
chatAdapter.notifyDataSetChanged()
@ -133,10 +133,12 @@ class ChatAdapter(private val messages: List<ChatMessage>) :
fun bind(message: ChatMessage) {
binding.usernameText.text = message.nickname
binding.messageText.text = message.message
binding.userIcon.setImageResource(when (message.nickname) {
"System" -> R.drawable.ic_system
else -> R.drawable.ic_user
})
binding.userIcon.setImageResource(
when (message.nickname) {
"System" -> R.drawable.ic_system
else -> R.drawable.ic_user
}
)
}
}
}

View file

@ -220,7 +220,7 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) {
val baseList = NetPlayManager.getPublicRooms()
val filteredList = baseList.filter { room ->
(!binding.chipHideFull.isChecked || room.members.size < room.maxPlayers) &&
(!binding.chipHideEmpty.isChecked || room.members.isNotEmpty())
(!binding.chipHideEmpty.isChecked || room.members.isNotEmpty())
}
if (binding.searchText.text.toString().isEmpty() &&
@ -245,7 +245,6 @@ class LobbyBrowser(context: Context) : BottomSheetDialog(context) {
it.score
}.map { it.item }
adapter.updateRooms(sortedList)
}
}

View file

@ -16,7 +16,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.PopupMenu
import android.widget.Toast
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.utils.CompatUtils
import org.yuzu.yuzu_emu.utils.GameHelper
import java.net.InetAddress
class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
private lateinit var adapter: NetPlayAdapter
@ -55,7 +53,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
when {
NetPlayManager.netPlayIsJoined() -> DialogMultiplayerLobbyBinding.inflate(layoutInflater)
NetPlayManager.netPlayIsJoined() -> DialogMultiplayerLobbyBinding.inflate(
layoutInflater
)
.apply {
setContentView(root)
adapter = NetPlayAdapter()
@ -77,7 +77,6 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
btnModeration.setOnClickListener {
showModerationDialog()
}
}
else -> {
@ -140,7 +139,8 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
inner class NetPlayAdapter : RecyclerView.Adapter<NetPlayAdapter.NetPlayViewHolder>() {
val netPlayItems = mutableListOf<NetPlayItems>()
abstract inner class NetPlayViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
abstract inner class NetPlayViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView),
View.OnClickListener {
init {
itemView.setOnClickListener(this)
@ -167,7 +167,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
visibility = if (iconRes != 0) {
setImageResource(iconRes)
View.VISIBLE
} else View.GONE
} else {
View.GONE
}
}
}
}
@ -186,14 +188,13 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun onClick(clicked: View) {}
private fun showPopupMenu(view: View) {
PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.menu_netplay_member, menu)
menu.findItem(R.id.action_kick).isEnabled = isModerator &&
netPlayItems.name != StringSetting.WEB_USERNAME.getString()
netPlayItems.name != StringSetting.WEB_USERNAME.getString()
menu.findItem(R.id.action_ban).isEnabled = isModerator &&
netPlayItems.name != StringSetting.WEB_USERNAME.getString()
netPlayItems.name != StringSetting.WEB_USERNAME.getString()
setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_kick) {
NetPlayManager.netPlayKickUser(netPlayItems.name)
@ -201,7 +202,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
} else if (item.itemId == R.id.action_ban) {
NetPlayManager.netPlayBanUser(netPlayItems.name)
true
} else false
} else {
false
}
}
show()
}
@ -360,12 +363,15 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
val visibilityList: List<String> = listOf(
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(
if (isCreateRoom) R.string.multiplayer_create_room
else R.string.multiplayer_join_room
if (isCreateRoom) {
R.string.multiplayer_create_room
} else {
R.string.multiplayer_join_room
}
)
// 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 {
setAdapter(
@ -501,8 +509,11 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
binding.btnConfirm.isEnabled = false
binding.btnConfirm.text =
activity.getString(
if (isCreateRoom) R.string.multiplayer_creating
else R.string.multiplayer_joining
if (isCreateRoom) {
R.string.multiplayer_creating
} else {
R.string.multiplayer_joining
}
)
// 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(
YuzuApplication.appContext,
if (isCreateRoom) R.string.multiplayer_create_room_success
else R.string.multiplayer_join_room_success,
if (isCreateRoom) {
R.string.multiplayer_create_room_success
} else {
R.string.multiplayer_join_room_success
},
Toast.LENGTH_LONG
).show()
@ -619,7 +633,9 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemBanListBinding.inflate(
LayoutInflater.from(parent.context), parent, false
LayoutInflater.from(parent.context),
parent,
false
)
return ViewHolder(binding)
}
@ -654,6 +670,5 @@ class NetPlayDialog(context: Context) : BottomSheetDialog(context) {
notifyItemRemoved(position)
}
}
}
}

View file

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

View file

@ -71,7 +71,7 @@ class ReleaseAdapter(
// truncates to 150 chars so it does not take up too much space.
var bodyPreview = release.body.take(150)
bodyPreview = bodyPreview.replace("#", "").removeSurrounding(" ");
bodyPreview = bodyPreview.replace("#", "").removeSurrounding(" ")
val body =
bodyPreview.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\n", "<br>")
@ -122,8 +122,11 @@ class ReleaseAdapter(
binding.imageDownloadsArrow.rotation = if (isVisible) 0f else 180f
binding.buttonToggleDownloads.text =
if (isVisible) activity.getString(R.string.show_downloads)
else activity.getString(R.string.hide_downloads)
if (isVisible) {
activity.getString(R.string.show_downloads)
} else {
activity.getString(R.string.hide_downloads)
}
}
binding.buttonToggleDownloads.setOnClickListener {
@ -139,9 +142,15 @@ class ReleaseAdapter(
release.artifacts.forEach { artifact ->
val button = MaterialButton(binding.root.context).apply {
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
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)
iconTint = ColorStateList.valueOf(
MaterialColors.getColor(
@ -199,7 +208,9 @@ class ReleaseAdapter(
input.copyTo(output)
}
}
?: throw IOException(context.getString(R.string.empty_response_body))
?: throw IOException(
context.getString(R.string.empty_response_body)
)
}
}
@ -211,7 +222,9 @@ class ReleaseAdapter(
val driverData = GpuDriverHelper.getMetadataFromZip(file)
val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(file.toUri())}"
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(
file.toUri()
)}"
if (GpuDriverHelper.copyDriverToInternalStorage(file.toUri())) {
driverViewModel.onDriverAdded(Pair(driverPath, driverData))
@ -254,7 +267,9 @@ class ReleaseAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReleaseViewHolder {
val binding = ItemReleaseBinding.inflate(
LayoutInflater.from(parent.context), parent, false
LayoutInflater.from(parent.context),
parent,
false
)
return ReleaseViewHolder(binding)
}
@ -264,4 +279,4 @@ class ReleaseAdapter(
}
override fun getItemCount(): Int = releases.size
}
}

View file

@ -16,4 +16,4 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
outRect.top = spacing
}
}
}
}

View file

@ -64,13 +64,12 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
SHOW_SHADERS_BUILDING("show_shaders_building"),
DEBUG_FLUSH_BY_LINE("flush_lines"),
USE_LRU_CACHE("use_lru_cache"),;
USE_LRU_CACHE("use_lru_cache");
external fun isRaiiEnabled(): Boolean
// external fun isFrameSkippingEnabled(): Boolean
external fun isFrameInterpolationEnabled(): Boolean
override fun getBoolean(needsGlobal: Boolean): Boolean =
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"),
LOGIN_SHARE_APPLET("login_share_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)

View file

@ -13,7 +13,7 @@ enum class StringSetting(override val key: String) : AbstractStringSetting {
DEVICE_NAME("device_name"),
WEB_TOKEN("eden_token"),
WEB_USERNAME("eden_username"),
WEB_USERNAME("eden_username")
;
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.StringSetting
import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
/**
@ -79,7 +78,7 @@ abstract class SettingsItem(
val needsRuntimeGlobal: Boolean
get() = NativeLibrary.isRunning() && !setting.global &&
!NativeConfig.isPerGameConfigLoaded()
!NativeConfig.isPerGameConfigLoaded()
val clearable: Boolean
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
@ -516,7 +515,6 @@ abstract class SettingsItem(
)
)
put(
SingleChoiceSetting(
IntSetting.RENDERER_VSYNC,
@ -731,7 +729,7 @@ abstract class SettingsItem(
val fastmem = object : AbstractBooleanSetting {
override fun getBoolean(needsGlobal: Boolean): Boolean =
BooleanSetting.FASTMEM.getBoolean() &&
BooleanSetting.FASTMEM_EXCLUSIVES.getBoolean()
BooleanSetting.FASTMEM_EXCLUSIVES.getBoolean()
override fun setBoolean(value: Boolean) {
BooleanSetting.FASTMEM.setBoolean(value)
@ -746,7 +744,7 @@ abstract class SettingsItem(
override var global: Boolean
get() {
return BooleanSetting.FASTMEM.global &&
BooleanSetting.FASTMEM_EXCLUSIVES.global
BooleanSetting.FASTMEM_EXCLUSIVES.global
}
set(value) {
BooleanSetting.FASTMEM.global = value

View file

@ -18,7 +18,7 @@ class SingleChoiceSetting(
@ArrayRes val choicesId: Int,
@ArrayRes val valuesId: Int,
val warnChoices: List<Int> = ArrayList(),
@StringRes val warningMessage: Int = 0,
@StringRes val warningMessage: Int = 0
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
override val type = TYPE_SINGLE_CHOICE

View file

@ -6,7 +6,6 @@
package org.yuzu.yuzu_emu.features.settings.model.view
import android.text.Editable
import androidx.annotation.StringRes
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?) {
val isValid = validator(s.toString())
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

@ -8,7 +8,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.edit
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
@ -16,13 +15,11 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
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.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins

View file

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

View file

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

View file

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

View file

@ -62,14 +62,14 @@ class DriverFetcherFragment : Fragment() {
val path: String = "",
val sort: Int = 0,
val useTagName: Boolean = false,
val sortMode: SortMode = SortMode.Default,
val sortMode: SortMode = SortMode.Default
)
private val repoList: List<DriverRepo> = listOf(
DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0),
DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1),
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(
@ -81,7 +81,7 @@ class DriverFetcherFragment : Fragment() {
IntRange(700, 710) to "KIMCHI 25.2.0_r5",
IntRange(711, 799) to "Mr. Purple T21",
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
@ -124,7 +124,9 @@ class DriverFetcherFragment : Fragment() {
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDriverFetcherBinding.inflate(inflater)
binding.badgeRecommendedDriver.text = recommendedDriver
@ -178,8 +180,12 @@ class DriverFetcherFragment : Fragment() {
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(requireActivity()).setTitle(getString(R.string.error_during_fetch))
.setMessage("${getString(R.string.failed_to_fetch)} ${name}:\n${e.message}")
MaterialAlertDialogBuilder(requireActivity()).setTitle(
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() }
.show()
@ -188,7 +194,9 @@ class DriverFetcherFragment : Fragment() {
}
val group = DriverGroup(
name, releases, sort
name,
releases,
sort
)
synchronized(driverGroups) {
@ -223,7 +231,9 @@ class DriverFetcherFragment : Fragment() {
binding.listDrivers.updateMargins(left = leftInsets, right = rightInsets)
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
@ -239,11 +249,13 @@ class DriverFetcherFragment : Fragment() {
var artifacts: List<Artifact> = ArrayList(),
var prerelease: Boolean = false,
var latest: Boolean = false,
var publishTime: LocalDateTime = LocalDateTime.now(),
var publishTime: LocalDateTime = LocalDateTime.now()
) {
companion object {
fun fromJsonArray(
jsonString: String, useTagName: Boolean, sortMode: SortMode
jsonString: String,
useTagName: Boolean,
sortMode: SortMode
): ArrayList<Release> {
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) {
// TODO: handle malformed input.
e.printStackTrace()
@ -324,6 +345,6 @@ class DriverFetcherFragment : Fragment() {
data class DriverGroup(
val name: String,
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.adapters.DriverAdapter
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.StringSetting
import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
@ -108,7 +107,9 @@ class DriverManagerFragment : Fragment() {
}
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 {

View file

@ -45,6 +45,7 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.window.layout.FoldingFeature
@ -52,7 +53,6 @@ import androidx.window.layout.WindowInfoTracker
import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import com.google.android.material.textview.MaterialTextView
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
@ -81,6 +81,13 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
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 kotlinx.coroutines.delay
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import java.io.File
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
@ -90,24 +97,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var perfStatsUpdater: (() -> Unit)? = null
private var socUpdater: (() -> Unit)? = null
private lateinit var cpuBackend: String
private lateinit var gpuDriver: String
private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!!
private val args by navArgs<EmulationFragmentArgs>()
private lateinit var game: Game
private var game: Game? = null
private val emulationViewModel: EmulationViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private var isInFoldableLayout = false
private var emulationStarted = false
private lateinit var gpuModel: 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) {
super.onAttach(context)
if (context is EmulationActivity) {
@ -125,9 +137,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onCreate(savedInstanceState)
updateOrientation()
val intentUri: Uri? = requireActivity().intent.data
var intentGame: Game? = null
if (intentUri != null) {
val intent = requireActivity().intent
val intentUri: Uri? = intent.data
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))) {
GameHelper.getGame(requireActivity().intent.data!!, false)
} else {
@ -135,38 +153,314 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
finishGameSetup()
}
/**
* Complete the game setup process (extracted for async custom settings handling)
*/
private fun finishGameSetup() {
try {
game = if (args.game != null) {
args.game!!
} else {
intentGame!!
val gameToUse = args.game ?: intentGame
if (gameToUse == null) {
Log.error("[EmulationFragment] No game found in arguments or intent")
Toast.makeText(
requireContext(),
R.string.no_game_present,
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
return
}
} catch (e: NullPointerException) {
game = gameToUse
} catch (e: Exception) {
Log.error("[EmulationFragment] Error during game setup: ${e.message}")
Toast.makeText(
requireContext(),
R.string.no_game_present,
"Setup error: ${e.message?.take(30) ?: "Unknown"}",
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
return
}
// Always load custom settings when launching a game from an intent
if (args.custom || intentGame != null) {
SettingsFile.loadCustomConfig(game)
NativeConfig.unloadPerGameConfig()
} else {
NativeConfig.reloadGlobalConfig()
try {
if (isCustomSettingsIntent) {
Log.info("[EmulationFragment] Using custom settings from intent")
} else if (intentGame != null && game != null) {
val customConfigFile = SettingsFile.getCustomSettingsFile(game!!)
if (customConfigFile.exists()) {
Log.info(
"[EmulationFragment] Found existing custom settings for ${game!!.title}, loading them"
)
SettingsFile.loadCustomConfig(game!!)
} else {
Log.info(
"[EmulationFragment] No custom settings found for ${game!!.title}, using global settings"
)
NativeConfig.reloadGlobalConfig()
}
} else {
val isCustomFromArgs = if (game != null && game == args.game) {
try {
args.custom
} catch (e: Exception) {
false
}
} else {
false
}
if (isCustomFromArgs && game != null) {
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
}
}
// Install the selected driver asynchronously as the game starts
driverViewModel.onLaunchGame()
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
emulationState = EmulationState(game.path) {
emulationState = EmulationState(game!!.path) {
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,
"This could be due to:\n• User cancelled configuration overwrite\n• Driver installation failed\n• Missing required drivers"
)
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 +481,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
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()
fwVersion = NativeLibrary.firmwareVersion()
@ -223,10 +531,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
})
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title)
titleView.text = game.title
}
updateGameTitle()
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val lockMode = IntSetting.LOCK_DRAWER.getInt()
@ -293,13 +599,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
R.id.menu_multiplayer -> {
emulationActivity?.displayMultiplayerDialog()
true
}
R.id.menu_controls -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
@ -368,8 +672,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
)
GameIconUtils.loadGameIcon(game, binding.loadingImage)
binding.loadingTitle.text = game.title
GameIconUtils.loadGameIcon(game!!, binding.loadingImage)
binding.loadingTitle.text = game!!.title
binding.loadingTitle.isSelected = true
binding.loadingText.isSelected = true
@ -408,7 +712,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.updateSurface()
// Setup overlays
updateShowStatsOverlay()
updateSocOverlay()
@ -418,7 +721,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val cpuBackendLabel = findViewById<TextView>(R.id.cpu_backend)
val vendorLabel = findViewById<TextView>(R.id.gpu_vendor)
titleView.text = game.title
titleView.text = game?.title ?: ""
cpuBackendLabel.text = NativeLibrary.getCpuBackend()
vendorLabel.text = NativeLibrary.getGpuDriver()
}
@ -456,16 +759,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
ViewUtils.showView(binding.loadingIndicator)
}
}
emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
if (it && emulationViewModel.programChanged.value != -1) {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
if (socUpdater != null) {
socUpdateHandler.removeCallbacks(socUpdater!!)
emulationViewModel.emulationStopped.collect(viewLifecycleOwner) { stopped ->
if (stopped && emulationViewModel.programChanged.value != -1) {
perfStatsRunnable?.let { runnable ->
perfStatsUpdateHandler.removeCallbacks(
runnable
)
}
socRunnable?.let { runnable -> socUpdateHandler.removeCallbacks(runnable) }
emulationState.changeProgram(emulationViewModel.programChanged.value)
emulationViewModel.setProgramChanged(-1)
emulationViewModel.setEmulationStopped(false)
@ -473,8 +775,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
if (it) startEmulation()
if (it && !NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
startEmulation()
}
}
driverViewModel.onLaunchGame()
}
private fun startEmulation(programIndex: Int = 0) {
@ -518,6 +824,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() {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
@ -634,7 +949,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val batteryTemp = getBatteryTemperature()
when (IntSetting.BAT_TEMPERATURE_UNIT.getInt(needsGlobal)) {
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 +963,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val battery: BatteryManager =
requireContext().getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val batteryIntent = requireContext().registerReceiver(null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val batteryIntent = requireContext().registerReceiver(
null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
val capacity = battery.getIntProperty(BATTERY_PROPERTY_CAPACITY)
val nowUAmps = battery.getIntProperty(BATTERY_PROPERTY_CURRENT_NOW)
@ -653,7 +975,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
status == BatteryManager.BATTERY_STATUS_FULL
if (isCharging) {
sb.append(" ${getString(R.string.charging)}")
@ -671,20 +993,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
if (BooleanSetting.PERF_OVERLAY_BACKGROUND.getBoolean(needsGlobal)) {
binding.showStatsOverlayText.setBackgroundResource(R.color.yuzu_transparent_black)
binding.showStatsOverlayText.setBackgroundResource(
R.color.yuzu_transparent_black
)
} else {
binding.showStatsOverlayText.setBackgroundResource(0)
}
binding.showStatsOverlayText.text = sb.toString()
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
perfStatsUpdateHandler.postDelayed(perfStatsRunnable!!, 800)
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
perfStatsRunnable = Runnable { perfStatsUpdater?.invoke() }
perfStatsUpdateHandler.post(perfStatsRunnable!!)
} else {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
}
perfStatsRunnable?.let { perfStatsUpdateHandler.removeCallbacks(it) }
}
}
@ -767,47 +1090,62 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
) {
sb.setLength(0)
if (BooleanSetting.SHOW_DEVICE_MODEL.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
if (BooleanSetting.SHOW_DEVICE_MODEL.getBoolean(
NativeConfig.isPerGameConfigLoaded()
)
) {
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(" | ")
sb.append(gpuModel)
}
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(" | ")
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(" | ")
sb.append(fwVersion)
}
binding.showSocOverlayText.text = sb.toString()
if (BooleanSetting.SOC_OVERLAY_BACKGROUND.getBoolean(NativeConfig.isPerGameConfigLoaded())) {
binding.showSocOverlayText.setBackgroundResource(R.color.yuzu_transparent_black)
if (BooleanSetting.SOC_OVERLAY_BACKGROUND.getBoolean(
NativeConfig.isPerGameConfigLoaded()
)
) {
binding.showSocOverlayText.setBackgroundResource(
R.color.yuzu_transparent_black
)
} else {
binding.showSocOverlayText.setBackgroundResource(0)
}
}
socUpdateHandler.postDelayed(socUpdater!!, 1000)
socUpdateHandler.postDelayed(socRunnable!!, 1000)
}
socUpdateHandler.post(socUpdater!!)
socRunnable = Runnable { socUpdater?.invoke() }
socUpdateHandler.post(socRunnable!!)
} else {
if (socUpdater != null) {
socUpdateHandler.removeCallbacks(socUpdater!!)
}
socRunnable?.let { socUpdateHandler.removeCallbacks(it) }
}
}
@SuppressLint("SourceLockedOrientationActivity")
private fun updateOrientation() {
emulationActivity?.let {
@ -919,11 +1257,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height)
emulationState.newSurface(holder.surface)
if (!emulationStarted) {
emulationStarted = true
emulationState.newSurface(holder.surface)
} else {
emulationState.newSurface(holder.surface)
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
emulationState.clearSurface()
emulationStarted = false
}
private fun showOverlayOptions() {
@ -1096,22 +1440,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
inputScaleSlider.apply {
valueTo = 150F
value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
addOnChangeListener(
Slider.OnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%"
setControlScale(value.toInt())
}
)
addOnChangeListener { _, value, _ ->
inputScaleValue.text = "${value.toInt()}%"
setControlScale(value.toInt())
}
}
inputOpacitySlider.apply {
valueTo = 100F
value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
addOnChangeListener(
Slider.OnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%"
setControlOpacity(value.toInt())
}
)
addOnChangeListener { _, value, _ ->
inputOpacityValue.text = "${value.toInt()}%"
setControlOpacity(value.toInt())
}
}
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
@ -1147,7 +1487,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
var left = 0
var right = 0
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
if (v.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
left = cutInsets.left
} else {
right = cutInsets.right
@ -1168,7 +1508,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
lateinit var emulationThread: Thread
init {
// Starting state is stopped.
state = State.STOPPED
}
@ -1176,7 +1515,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val isStopped: Boolean
get() = state == State.STOPPED
// Getters for the current state
@get:Synchronized
val isPaused: Boolean
get() = state == State.PAUSED
@ -1196,7 +1534,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
// State changing methods
@Synchronized
fun pause() {
if (state != State.PAUSED) {

View file

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

View file

@ -134,10 +134,10 @@ class InstallableFragment : Fragment() {
install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
),
Installable(
R.string.uninstall_firmware,
R.string.uninstall_firmware_description,
install = { mainActivity.uninstallFirmware() }
),
R.string.uninstall_firmware,
R.string.uninstall_firmware_description,
install = { mainActivity.uninstallFirmware() }
),
Installable(
R.string.install_prod_keys,
R.string.install_prod_keys_description,

View file

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

View file

@ -78,7 +78,6 @@ class SetupFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
mainActivity = requireActivity() as MainActivity
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
@ -129,7 +128,7 @@ class SetupFragment : Fragment() {
0,
{
if (NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
.areNotificationsEnabled()
) {
StepState.COMPLETE
} else {

View file

@ -22,7 +22,11 @@ class MidScreenSwipeRefreshLayout @JvmOverloads constructor(
MotionEvent.ACTION_DOWN -> {
startX = ev.x
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 rightBound = leftBound + (width * center_fraction)
allowRefresh = startX >= leftBound && startX <= rightBound
@ -30,4 +34,4 @@ class MidScreenSwipeRefreshLayout @JvmOverloads constructor(
}
return if (allowRefresh) super.onInterceptTouchEvent(ev) else false
}
}
}

View file

@ -141,7 +141,7 @@ class GamesViewModel : ViewModel() {
}
}
fun addFolder(gameDir: GameDir, savedFromGameFragment: Boolean) =
fun addFolder(gameDir: GameDir, savedFromGameFragment: Boolean) =
viewModelScope.launch {
withContext(Dispatchers.IO) {
NativeConfig.addGameDir(gameDir)

View file

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

View file

@ -70,7 +70,6 @@ object NetPlayManager {
val gameName: String
)
private var messageListener: ((Int, String) -> Unit)? = null
private var adapterRefreshListener: ((Int, String) -> Unit)? = null
@ -199,7 +198,6 @@ object NetPlayManager {
}
}
Handler(Looper.getMainLooper()).post {
if (!isChatOpen) {
// TODO(alekpop, crueter): Improve this, potentially a drawer at the top?
@ -207,7 +205,6 @@ object NetPlayManager {
}
}
messageListener?.invoke(type, msg)
adapterRefreshListener?.invoke(type, msg)
}
@ -218,19 +215,29 @@ object NetPlayManager {
NetPlayStatus.LOST_CONNECTION -> context.getString(R.string.multiplayer_lost_connection)
NetPlayStatus.NAME_COLLISION -> context.getString(R.string.multiplayer_name_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_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.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.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.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_JOINING -> context.getString(R.string.multiplayer_room_joining)
NetPlayStatus.ROOM_JOINED -> context.getString(R.string.multiplayer_room_joined)
@ -247,7 +254,9 @@ object NetPlayManager {
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
else -> ""
}

View file

@ -7,20 +7,13 @@ import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.inputmethod.InputMethodManager
import android.widget.ImageButton
import android.widget.PopupMenu
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
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.WindowInsetsCompat
import androidx.core.view.updatePadding
@ -111,7 +104,7 @@ class GamesFragment : Fragment() {
}
gameAdapter = GameAdapter(
requireActivity() as AppCompatActivity,
requireActivity() as AppCompatActivity
)
applyGridGamesBinding()
@ -238,7 +231,9 @@ class GamesFragment : Fragment() {
override fun onResume() {
super.onResume()
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())
if (searchTerm.isEmpty()) {
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(filteredList)
((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(
filteredList
)
gamesViewModel.setFilteredGames(filteredList)
return
}
@ -464,7 +461,9 @@ class GamesFragment : Fragment() {
// Always set margin as original + insets
mlpHeader.leftMargin = (originalHeaderLeftMargin ?: 0) + leftInset
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.noticeText.updatePadding(bottom = spacingNavigation)

View file

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

when I'm in an unreadable code competition and my opponent is Kotlin

when I'm in an unreadable code competition and my opponent is Kotlin
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

@ -197,7 +197,9 @@ object FileUtil {
*/
fun getFilename(uri: Uri): String {
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

View file

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

View file

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

View file

@ -23,10 +23,12 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
object ThemeHelper {
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
private var listener: SharedPreferences.OnSharedPreferenceChangeListener? = null
private val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
private val preferences = PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext
)
fun setTheme(activity: AppCompatActivity) {
setThemeMode(activity)
@ -52,6 +54,7 @@ object ThemeHelper {
private fun getSelectedStaticThemeColor(): Int {
val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
val themes = arrayOf(
R.style.Theme_Eden_Main,
R.style.Theme_Yuzu_Main_Violet,
R.style.Theme_Yuzu_Main_Blue,
R.style.Theme_Yuzu_Main_Cyan,
@ -120,7 +123,11 @@ object ThemeHelper {
fun ThemeChangeListener(activity: AppCompatActivity) {
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) {
activity.recreate()
}

View file

@ -1,6 +1,9 @@
// 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.ui
import android.content.Context
@ -53,7 +56,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
var flingMultiplier: Float = 1f
public var pendingScrollAfterReload: Boolean = false
var pendingScrollAfterReload: Boolean = false
var useCustomDrawingOrder: Boolean = false
set(value) {
@ -76,7 +79,11 @@ class CarouselRecyclerView @JvmOverloads constructor(
private fun getLayoutManagerCenter(layoutManager: RecyclerView.LayoutManager): Int {
return if (layoutManager is LinearLayoutManager) {
calculateCenter(layoutManager.width, layoutManager.paddingStart, layoutManager.paddingEnd)
calculateCenter(
layoutManager.width,
layoutManager.paddingStart,
layoutManager.paddingEnd
)
} else {
width / 2
}
@ -121,13 +128,13 @@ class CarouselRecyclerView @JvmOverloads constructor(
fun shapingFunction(x: Float, option: Int = 0): Float {
return when (option) {
0 -> 1f //Off
1 -> 1f - x //linear descending
2 -> (1f - x) * (1f - x) //Ease out
3 -> if (x < 0.05f) 1f else (1f-x) * 0.8f
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
else -> 1f //Default to Off
0 -> 1f // Off
1 -> 1f - x // linear descending
2 -> (1f - x) * (1f - x) // Ease out
3 -> if (x < 0.05f) 1f else (1f - x) * 0.8f
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
else -> 1f // Default to Off
}
}
@ -143,20 +150,36 @@ class CarouselRecyclerView @JvmOverloads constructor(
val center = getRecyclerViewCenter()
val distance = abs(getChildDistanceToCenter(child))
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 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 scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f)
val maxDistance = width / 2f
val alphaInput = (distance / maxDistance).coerceIn(0f, 1f)
val internalBordersAlpha = resources.getFraction(R.fraction.carousel_bordercards_alpha, 1, 1)
val borderAlpha = preferences.getFloat(CAROUSEL_BORDERCARDS_ALPHA, internalBordersAlpha).coerceIn(0f, 1f)
val internalBordersAlpha = resources.getFraction(
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 alphaShapeSetting = preferences.getInt(CAROUSEL_CARDS_ALPHA_SHAPE, internalAlphaShapeSetting)
val alphaShapeSetting = preferences.getInt(
CAROUSEL_CARDS_ALPHA_SHAPE,
internalAlphaShapeSetting
)
val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting)
val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f)
@ -185,16 +208,33 @@ class CarouselRecyclerView @JvmOverloads constructor(
val insets = rootWindowInsets
val bottomInset = insets?.getInsets(android.view.WindowInsets.Type.systemBars())?.bottom ?: 0
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()
gameAdapter?.setCardSize(cardSize)
val internalOverlapFactor = resources.getFraction(R.fraction.carousel_overlap_factor, 1, 1)
overlapFactor = preferences.getFloat(CAROUSEL_OVERLAP_FACTOR, internalOverlapFactor).coerceIn(0f, 1f)
val internalOverlapFactor = resources.getFraction(
R.fraction.carousel_overlap_factor,
1,
1
)
overlapFactor = preferences.getFloat(CAROUSEL_OVERLAP_FACTOR, internalOverlapFactor).coerceIn(
0f,
1f
)
overlapPx = (cardSize * overlapFactor).toInt()
val internalFlingMultiplier = resources.getFraction(R.fraction.carousel_fling_multiplier, 1, 1)
flingMultiplier = preferences.getFloat(CAROUSEL_FLING_MULTIPLIER, internalFlingMultiplier).coerceIn(1f, 5f)
val internalFlingMultiplier = resources.getFraction(
R.fraction.carousel_fling_multiplier,
1,
1
)
flingMultiplier = preferences.getFloat(
CAROUSEL_FLING_MULTIPLIER,
internalFlingMultiplier
).coerceIn(1f, 5f)
gameAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
@ -290,20 +330,28 @@ class CarouselRecyclerView @JvmOverloads constructor(
View.FOCUS_LEFT -> {
if (position > 0) {
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
if (!repeatDetected) { //ensures the first run
if (!repeatDetected) { // ensures the first run
val offset = focused.width - overlapPx
smoothScrollBy(-offset, 0)
}
findViewHolderForAdapterPosition(position - 1)?.itemView ?: super.focusSearch(focused, direction)
findViewHolderForAdapterPosition(position - 1)?.itemView ?: super.focusSearch(
focused,
direction
)
} else {
focused
}
}
View.FOCUS_RIGHT -> {
if (position < itemCount - 1) {
findViewHolderForAdapterPosition(position + 1)?.itemView ?: super.focusSearch(focused, direction)
findViewHolderForAdapterPosition(position + 1)?.itemView ?: super.focusSearch(
focused,
direction
)
} else {
focused
}
@ -341,7 +389,10 @@ class CarouselRecyclerView @JvmOverloads constructor(
inner class OverlappingDecoration(private val overlap: Int) : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView, state: State
outRect: Rect,
view: View,
parent: RecyclerView,
state: State
) {
val position = parent.getChildAdapterPosition(view)
if (position > 0) {
@ -378,12 +429,17 @@ class CarouselRecyclerView @JvmOverloads constructor(
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(
layoutManager: RecyclerView.LayoutManager,
targetView: View
): IntArray? {
if (layoutManager !is LinearLayoutManager) return super.calculateDistanceToFinalSnap(layoutManager, targetView)
if (layoutManager !is LinearLayoutManager) {
return super.calculateDistanceToFinalSnap(
layoutManager,
targetView
)
}
val out = IntArray(2)
out[0] = getChildDistanceToCenter(targetView).toInt()
out[1] = 0
@ -399,11 +455,14 @@ class CarouselRecyclerView @JvmOverloads constructor(
if (layoutManager !is LinearLayoutManager) return RecyclerView.NO_POSITION
val closestPosition = this@CarouselRecyclerView.getClosestChildPosition()
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 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
}
}
}
}

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:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_game_carousel"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:cardPreventCornerOverlap="true"
android:clipChildren="true"
android:layout_margin="0dp"
app:strokeColor="@android:color/transparent"
app:strokeWidth="0dp"
android:alpha="0">
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="1dp"
app:strokeColor="@color/eden_border">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp">
<ImageView
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:contentDescription="@string/game_image_desc"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Eden.CarouselImage"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -43,4 +46,4 @@
android:text="Game Title" />
</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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:clipChildren="false"
>
@ -44,7 +43,10 @@
style="?attr/materialCardViewFilledStyle"
android:layout_width="match_parent"
android:layout_height="48dp"
app:cardCornerRadius="21dp"
app:cardCornerRadius="24dp"
app:cardBackgroundColor="?attr/colorSurfaceVariant"
app:strokeColor="?attr/colorOutline"
app:strokeWidth="1dp"
>
<LinearLayout
@ -100,6 +102,9 @@
android:layout_width="42dp"
android:layout_height="42dp"
app:cardCornerRadius="21dp"
app:cardBackgroundColor="@color/eden_surface_variant"
app:strokeColor="@color/eden_border"
app:strokeWidth="1dp"
>
<ImageView
@ -123,6 +128,9 @@
android:layout_width="42dp"
android:layout_height="42dp"
app:cardCornerRadius="21dp"
app:cardBackgroundColor="@color/eden_surface_variant"
app:strokeColor="@color/eden_border"
app:strokeWidth="1dp"
>
<ImageView
@ -146,6 +154,9 @@
android:layout_width="42dp"
android:layout_height="42dp"
app:cardCornerRadius="21dp"
app:cardBackgroundColor="@color/eden_surface_variant"
app:strokeColor="@color/eden_border"
app:strokeWidth="1dp"
>
<ImageView
@ -210,9 +221,9 @@
app:icon="@drawable/ic_cartridge_outline"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textColor="?attr/colorOnPrimaryContainer"
app:backgroundTint="?attr/colorPrimaryContainer"
app:iconTint="?attr/colorOnPrimaryContainer"
android:textColor="?attr/colorOnPrimary"
app:backgroundTint="?attr/colorPrimary"
app:iconTint="?attr/colorOnPrimary"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

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

View file

@ -5,14 +5,16 @@
android:id="@+id/coordinator_about"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:touchscreenBlocksFocus="false">
android:touchscreenBlocksFocus="false"
android:background="@android:color/transparent"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_about"
@ -37,193 +39,206 @@
android:id="@+id/content_about"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:orientation="horizontal"
android:padding="24dp">
<ImageView
android:id="@+id/image_logo"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:padding="20dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="32dp"
android:src="@drawable/ic_yuzu_title" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
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:layout_marginHorizontal="24dp"
android:text="@string/about"
android:textAlignment="viewStart" />
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:text="@string/about_app_description"
android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/about"
android:textAlignment="viewStart" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/about_app_description"
android:textAlignment="viewStart" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_contributors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
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:layout_marginHorizontal="24dp"
android:text="@string/contributors"
android:textAlignment="viewStart" />
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:text="@string/contributors_description"
android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/contributors"
android:textAlignment="viewStart" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/contributors_description"
android:textAlignment="viewStart" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_licenses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
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:layout_marginHorizontal="24dp"
android:text="@string/licenses"
android:textAlignment="viewStart" />
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:text="@string/licenses_description"
android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/licenses"
android:textAlignment="viewStart" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/licenses_description"
android:textAlignment="viewStart" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_version_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
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:layout_marginHorizontal="24dp"
android:text="@string/build"
android:textAlignment="viewStart" />
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="20dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="6dp"
android:textAlignment="viewStart"
tools:text="abc123" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/build"
android:textAlignment="viewStart" />
</LinearLayout>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textAlignment="viewStart"
tools:text="abc123" />
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="16dp"
android:gravity="center_horizontal"
android:layout_marginTop="24dp"
android:gravity="start"
android:orientation="horizontal">
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/button_discord"
style="?attr/materialIconButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/EdenButton.Secondary"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="12dp"
app:icon="@drawable/ic_discord"
app:iconGravity="textEnd"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface" />
app:iconPadding="0dp" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/button_website"
style="?attr/materialIconButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/EdenButton.Secondary"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="12dp"
app:icon="@drawable/ic_website"
app:iconGravity="textEnd"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface" />
app:iconPadding="0dp" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/button_github"
style="?attr/materialIconButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/EdenButton.Secondary"
android:layout_width="56dp"
android:layout_height="56dp"
app:icon="@drawable/ic_github"
app:iconGravity="textEnd"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="?attr/colorOnSurface" />
app:iconPadding="0dp" />
</LinearLayout>
@ -233,4 +248,4 @@
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

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

View file

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

View file

@ -6,7 +6,7 @@
android:id="@+id/coordinator_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
android:background="?android:attr/colorBackground">
<androidx.fragment.app.FragmentContainerView
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"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle"
style="@style/EdenCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
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:app="http://schemas.android.com/apk/res-auto"
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_height="match_parent"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:layout_margin="4dp"
app:strokeColor="@android:color/transparent"
app:strokeWidth="0dp">
app:cardCornerRadius="16dp"
app:cardPreventCornerOverlap="true"
android:clipChildren="true"
android:layout_margin="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<ImageView
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_game_screen"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:contentDescription="@string/game_image_desc"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Eden.CarouselImage"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -29,12 +33,14 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title"
style="@style/TextAppearance.Material3.TitleMedium"
style="@style/SynthwaveText.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:requiresFadingEdge="horizontal"
android:textAlignment="center"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -42,4 +48,4 @@
android:text="Game Title" />
</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:tools="http://schemas.android.com/tools"
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"
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_height="wrap_content"
android:layout_gravity="center"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:clipToPadding="true"
android:focusable="true"
android:transitionName="card_game"
app:cardCornerRadius="4dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp">
app:cardCornerRadius="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
@ -33,17 +35,19 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearance.Material3.Corner.Medium"
android:scaleType="centerCrop"
tools:src="@drawable/default_icon" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title"
style="@style/TextAppearance.Material3.TitleMedium"
style="@style/SynthwaveText.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:requiresFadingEdge="horizontal"
android:textAlignment="center"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/image_game_screen"
app:layout_constraintStart_toStartOf="@+id/image_game_screen"
app:layout_constraintTop_toBottomOf="@+id/image_game_screen"
@ -51,6 +55,6 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</org.yuzu.yuzu_emu.views.GradientBorderCardView>
</FrameLayout>

View file

@ -1,18 +1,18 @@
<?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:tools="http://schemas.android.com/tools"
android:id="@+id/card_game_list"
style="?attr/materialCardViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:transitionName="card_game"
app:cardCornerRadius="14dp"
app:cardElevation="0dp">
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:cardBackgroundColor="@color/eden_card_background"
app:strokeWidth="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@ -31,7 +31,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title"
style="@style/TextAppearance.Material3.TitleMedium"
style="@style/SynthwaveText.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
@ -39,6 +39,7 @@
android:textAlignment="viewStart"
android:singleLine="true"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/text_game_developer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/image_game_screen"
@ -48,7 +49,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_developer"
style="@style/TextAppearance.Material3.BodySmall"
style="@style/SynthwaveText.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
@ -56,6 +57,7 @@
android:textAlignment="viewStart"
android:singleLine="true"
android:textSize="12sp"
android:alpha="0.7"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/image_game_screen"
@ -64,4 +66,4 @@
</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"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewStyle"
style="@style/EdenCard"
android:id="@+id/option_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:layout_marginHorizontal="12dp"
android:background="?attr/colorSurface"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="8dp"
android:clickable="true"
android:focusable="true"
app:cardElevation="0dp">
app:cardCornerRadius="16dp">
<LinearLayout
android:id="@+id/option_layout"
@ -25,7 +24,7 @@
android:layout_height="24dp"
android:layout_marginStart="24dp"
android:layout_gravity="center_vertical"
app:tint="?attr/colorOnSurface" />
app:tint="?attr/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
@ -35,7 +34,7 @@
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodyMedium"
style="@style/SynthwaveText.Body"
android:id="@+id/option_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -45,17 +44,18 @@
tools:text="@string/install_prod_keys" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.BodySmall"
style="@style/SynthwaveText.Body"
android:id="@+id/option_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:alpha="0.8"
tools:text="@string/install_prod_keys_description" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.LabelMedium"
style="@style/SynthwaveText.Secondary"
android:id="@+id/option_detail"
android:layout_width="match_parent"
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"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/materialCardViewOutlinedStyle"
style="@style/EdenCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Shader-Backend</string>
<string name="shader_backend_description">Methode zur Shader-Kompilierung</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<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_description">Elegir cómo se compilan shaders</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulación NVDEC</string>

View file

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

View file

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

View file

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

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Shader backend</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_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<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_description">Pilih cara shader dikompilasi dan diterjemahkan untuk GPU Anda.</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulasi NVDEC</string>

View file

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

View file

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

View file

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

View file

@ -153,8 +153,8 @@
<string name="shader_backend">Shader-backend</string>
<string name="shader_backend_description">Velg hvordan shadere kompileres</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<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_description">Wybierz metodę kompilacji shaderów.</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<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_description">Define como shaders são compilados</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<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_description">Método de compilação de shaders.</string>
<string name="shader_backend_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<string name="nvdec_emulation">Emulação NVDEC</string>

View file

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

View file

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

View file

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

View file

@ -153,8 +153,8 @@
<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_glsl">GLSL</string>
<string name="shader_backend_glasm">GLASM</string>string>
<string name="shader_backend_spirv">Spir-V</string>string>
<string name="shader_backend_glasm">GLASM</string>
<string name="shader_backend_spirv">Spir-V</string>
<!-- NVDEC Emulation -->
<string name="nvdec_emulation">Giả lập NVDEC</string>

View file

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

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