Compare commits

...
Sign in to create a new pull request.

33 commits

Author SHA1 Message Date
2c4d5f7a81 refactor: wip, trying to fix intent launching 2025-07-27 14:05:20 +02:00
e99b129cc2 style: remove unused DriverResolver and refactor driver handling in CustomSettingsHandler
- Deleted `DriverResolver`
- Moved and streamlined driver path extraction logic into `CustomSettingsHandler`.
- Improved string resource usage and ensured consistent formatting across dialogs.
2025-07-27 13:35:55 +02:00
3e3e35f558 fix: exclude Android from gamemode sources in CMakeLists.txt 2025-07-27 13:14:44 +02:00
6cb45e67fc style: add GradientBorderCardView with theme-aware gradient border implementation 2025-07-27 13:14:18 +02:00
fcd1b0ecc5 style: implement Eden theme 2025-07-27 13:13:26 +02:00
716e30e204 style: update SPDX license information in CarouselRecyclerView 2025-07-25 20:32:00 +02:00
1a5b6083e7 style: code formatting and remove unused imports in MainActivity 2025-07-25 20:20:57 +02:00
9838ebaef9 style: format code and remove unused imports in GamesFragment 2025-07-25 18:50:54 +02:00
f71872d937 style: remove redundant public modifier and fix variable declaration 2025-07-25 18:49:18 +02:00
6e88a9f1f6 style: format code and remove unnecessary blank lines and imports 2025-07-25 18:46:01 +02:00
0ea1870bbc feat: driver installation dialogs with string resources 2025-07-25 18:38:02 +02:00
6b65f95cfa style: remove unnecessary blank line in build.gradle.kts 2025-07-25 18:34:27 +02:00
5008b23062 fix: formatting in DriverGroupAdapter.kt and unused iimport 2025-07-25 18:34:04 +02:00
0dda4fd3ce style: format code 2025-07-25 18:33:29 +02:00
eb3d221ec6 Fix malformed XML in string translations
The `shader_backend_glasm` and `shader_backend_spirv` strings had an extra `string>` tag in multiple language files. This commit removes the extraneous tag.

Additionally, new strings related to Intent Launch, Custom Config, and Driver handling have been added to the default `strings.xml` file.

* src/android/app/src/main/res/values-ru/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-fr/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-pt-rPT/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-id/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-pl/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values/strings.xml: Remove extraneous `string>` tag and add new strings.
* src/android/app/src/main/res/values-fa/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-ar/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-he/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-ckb/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-sr/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-it/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-ja/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-vi/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-cs/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-hu/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-nb/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-uk/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-ko/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-es/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-de/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-zh-rCN/strings.xml: Remove extraneous `string>` tag.
* src/android/app/src/main/res/values-pt-rBR/strings.xml: Remove extraneous `string>` tag.
2025-07-25 18:29:58 +02:00
953e07d1d7 Fix typos in Chinese (Traditional) translation
* src/android/app/src/main/res/values-zh-rTW/strings.xml: Remove extra 'string' from shader backend options.
2025-07-25 18:28:22 +02:00
e3acf3051c Merge branch 'feat/android-emuready' of eden:eden-emu/eden into feat/android-emuready 2025-07-23 17:57:53 +02:00
4155efc7b9 Merge branch 'master' of eden:eden-emu/eden into feat/android-emuready 2025-07-23 17:57:24 +02:00
d76218baa1 Add user feedback with Toast messages for custom settings and driver handling
* Introduced Toast messages across `CustomSettingsHandler`, `DriverResolver`, and `EmulationFragment` to improve user interaction and feedback for key operations.
* Enhanced error handling and confirmation dialogs, including options to launch with default settings when custom settings fail.
2025-07-23 09:58:31 +02:00
e2f2cfa476 refactor: extract and streamline EmuReady intent handling
* Moved custom settings logic from `onCreate` to `handleEmuReadyIntent` for better readability.
* Added `showLaunchConfirmationDialog` to confirm game launch with or without custom settings.
* Updated `CustomSettingsHandler.findGameByTitleId` to show user feedback via `Toast`.
* Ensured improved separation of concerns and reusable methods.
2025-07-23 09:58:31 +02:00
1eeab7e19e fix: suppress warning for predictive back gesture
* src/android/app/src/main/AndroidManifest.xml: Set targetApi to 33 and
  enable onBackInvokedCallback to support the Android 13 predictive back gesture.
  Also, add the tools namespace.
2025-07-23 09:58:31 +02:00
9adb75b0dc fix: refactor EmulationFragment
*handler callbacks using nullable Runnables
  * Remove unused `cpuBackend` and `gpuDriver` variables.
  * Use lambda syntax for `Slider.OnChangeListener`.
  * Remove unused imports.
2025-07-23 09:58:31 +02:00
ab50b5cc9a fix: deprecating warning for getLayoutDirection and LAYOUT_DIRECTION_LTR 2025-07-23 09:58:31 +02:00
e00abd0281 chore: removed unnecessary typecasting 2025-07-23 09:58:31 +02:00
e1efec5a24 Support loading custom game settings via intent
This commit introduces the ability to launch games with custom configurations supplied via an Android intent. This allows external applications to provide specific settings for a game at launch time.

Key changes include:

*   **`CustomSettingsHandler.kt`**: A new class responsible for:
    *   Processing incoming intents with custom settings.
    *   Finding the target game in the user's library by its title ID.
    *   Writing the custom settings to a per-game INI file (`config/custom/<title_id>.ini`).
    *   Handling potential conflicts if a custom configuration already exists, prompting the user to overwrite or cancel.
    *   Integrating with `DriverResolver` to check for and handle required GPU drivers specified in the custom settings.
    *   Initializing the native per-game configuration.
*   **`DriverResolver.kt`**: A new utility class for managing GPU drivers specified in custom settings:
    *   Extracts the driver path from the custom settings INI content.
    *   Checks if the required driver exists locally.
    *   If not found locally, searches for the driver in predefined GitHub repositories (Mr. Purple Turnip, GameHub Adreno 8xx, KIMCHI Turnip, Weab-Chan Freedreno).
    *   Prompts the user to download and install the missing driver if found online.
    *   Handles automatic download and installation of drivers using `DriverViewModel`.
    *   Notifies the user if a required driver cannot be found or installed.
*   **`AndroidManifest.xml`**:
    *   Added a new intent filter for the action `dev.eden.eden_emulator.LAUNCH_WITH_CUSTOM_CONFIG` to `EmulationActivity`. This allows the app to respond to custom settings intents.
*   **`EmulationFragment.kt`**:
    *   Modified `onCreate` to detect and handle the new custom settings intent.
    *   If a custom settings intent is received:
        *   It uses `CustomSettingsHandler.applyCustomSettingsWithDriverCheck` to process the settings asynchronously. This allows for driver checks and user interaction (e.g., overwrite confirmation, driver installation).
        *   Displays appropriate error messages via `Toast` if custom settings processing fails (e.g., game not found, driver issues).
        *   The game is then launched with the applied custom settings.
    *   If a regular file intent or navigation arguments are used, the existing logic for loading game configurations (including custom per-game configs) is retained.
    *   Ensures that per-game configurations are correctly loaded or unloaded based on how the game is launched.
2025-07-23 09:58:31 +02:00
c77cf1d0e8 Add user feedback with Toast messages for custom settings and driver handling
* Introduced Toast messages across `CustomSettingsHandler`, `DriverResolver`, and `EmulationFragment` to improve user interaction and feedback for key operations.
* Enhanced error handling and confirmation dialogs, including options to launch with default settings when custom settings fail.
2025-07-20 18:11:55 +02:00
3abc4ee7db refactor: extract and streamline EmuReady intent handling
* Moved custom settings logic from `onCreate` to `handleEmuReadyIntent` for better readability.
* Added `showLaunchConfirmationDialog` to confirm game launch with or without custom settings.
* Updated `CustomSettingsHandler.findGameByTitleId` to show user feedback via `Toast`.
* Ensured improved separation of concerns and reusable methods.
2025-07-13 07:31:22 +02:00
3376e86e47 fix: suppress warning for predictive back gesture
* src/android/app/src/main/AndroidManifest.xml: Set targetApi to 33 and
  enable onBackInvokedCallback to support the Android 13 predictive back gesture.
  Also, add the tools namespace.
2025-07-12 21:11:34 +02:00
4ae2e31667 Merge branch 'master' of eden:eden-emu/eden into feat/android-emuready 2025-07-12 20:48:53 +02:00
c03b93e1f0 fix: refactor EmulationFragment
*handler callbacks using nullable Runnables
  * Remove unused `cpuBackend` and `gpuDriver` variables.
  * Use lambda syntax for `Slider.OnChangeListener`.
  * Remove unused imports.
2025-07-12 20:47:37 +02:00
4c66f6aceb fix: deprecating warning for getLayoutDirection and LAYOUT_DIRECTION_LTR 2025-07-12 20:46:33 +02:00
7fa40791c6 chore: removed unnecessary typecasting 2025-07-12 18:25:28 +02:00
eb720ef074 Support loading custom game settings via intent
This commit introduces the ability to launch games with custom configurations supplied via an Android intent. This allows external applications to provide specific settings for a game at launch time.

Key changes include:

*   **`CustomSettingsHandler.kt`**: A new class responsible for:
    *   Processing incoming intents with custom settings.
    *   Finding the target game in the user's library by its title ID.
    *   Writing the custom settings to a per-game INI file (`config/custom/<title_id>.ini`).
    *   Handling potential conflicts if a custom configuration already exists, prompting the user to overwrite or cancel.
    *   Integrating with `DriverResolver` to check for and handle required GPU drivers specified in the custom settings.
    *   Initializing the native per-game configuration.
*   **`DriverResolver.kt`**: A new utility class for managing GPU drivers specified in custom settings:
    *   Extracts the driver path from the custom settings INI content.
    *   Checks if the required driver exists locally.
    *   If not found locally, searches for the driver in predefined GitHub repositories (Mr. Purple Turnip, GameHub Adreno 8xx, KIMCHI Turnip, Weab-Chan Freedreno).
    *   Prompts the user to download and install the missing driver if found online.
    *   Handles automatic download and installation of drivers using `DriverViewModel`.
    *   Notifies the user if a required driver cannot be found or installed.
*   **`AndroidManifest.xml`**:
    *   Added a new intent filter for the action `dev.eden.eden_emulator.LAUNCH_WITH_CUSTOM_CONFIG` to `EmulationActivity`. This allows the app to respond to custom settings intents.
*   **`EmulationFragment.kt`**:
    *   Modified `onCreate` to detect and handle the new custom settings intent.
    *   If a custom settings intent is received:
        *   It uses `CustomSettingsHandler.applyCustomSettingsWithDriverCheck` to process the settings asynchronously. This allows for driver checks and user interaction (e.g., overwrite confirmation, driver installation).
        *   Displays appropriate error messages via `Toast` if custom settings processing fails (e.g., game not found, driver issues).
        *   The game is then launched with the applied custom settings.
    *   If a regular file intent or navigation arguments are used, the existing logic for loading game configurations (including custom per-game configs) is retained.
    *   Ensures that per-game configurations are correctly loaded or unloaded based on how the game is launched.
2025-07-12 13:02:48 +02:00
107 changed files with 2223 additions and 687 deletions

View file

@ -155,7 +155,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
@ -421,7 +419,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

@ -63,13 +63,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,
@ -724,7 +722,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)
@ -739,7 +737,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
@ -1056,7 +1054,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,308 @@ 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) {
completeViewSetup()
val driverReady = driverViewModel.isInteractionAllowed.value
if (driverReady && !NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
Log.info("[EmulationFragment] Starting emulation after async intent setup - driver ready")
startEmulation()
}
}
} catch (e: Exception) {
Log.error("[EmulationFragment] Error in finishGameSetup: ${e.message}")
requireActivity().finish()
return@withContext
}
}
} else {
Log.error("[EmulationFragment] No valid game found after processing intent")
Toast.makeText(
requireContext(),
getString(R.string.failed_to_initialize_game),
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
}
} catch (e: Exception) {
Log.error("[EmulationFragment] Error processing EmuReady intent: ${e.message}")
Toast.makeText(
requireContext(),
"Error: ${e.message?.take(50) ?: "Unknown error"}",
Toast.LENGTH_LONG
).show()
requireActivity().finish()
}
}
} else {
Log.error("[EmulationFragment] EmuReady intent missing title_id")
Toast.makeText(
requireContext(),
"Invalid request: missing title ID",
Toast.LENGTH_SHORT
).show()
requireActivity().finish()
}
}
/**
* Show confirmation dialog for EmuReady game launches
*/
private suspend fun showLaunchConfirmationDialog(gameTitle: String, hasCustomSettings: Boolean): Boolean {
return suspendCoroutine { continuation ->
requireActivity().runOnUiThread {
val message = if (hasCustomSettings) {
getString(
R.string.custom_intent_launch_message_with_settings,
gameTitle
)
} else {
getString(R.string.custom_intent_launch_message, gameTitle)
}
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.custom_intent_launch_title))
.setMessage(message)
.setPositiveButton(getString(R.string.launch)) { _, _ ->
continuation.resume(true)
}
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
continuation.resume(false)
}
.setCancelable(false)
.show()
}
}
}
/**
* Ask user if they want to launch with default settings when custom settings fail
*/
private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean {
return suspendCoroutine { continuation ->
requireActivity().runOnUiThread {
MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.custom_settings_failed_title))
.setMessage(
getString(R.string.custom_settings_failed_message, gameTitle, errorMessage)
)
.setPositiveButton(getString(R.string.launch_with_default_settings)) { _, _ ->
continuation.resume(true)
}
.setNegativeButton(getString(R.string.cancel)) { _, _ ->
continuation.resume(false)
}
.setCancelable(false)
.show()
}
}
}
/**
@ -187,6 +475,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return
}
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 +525,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 +593,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 +666,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 +706,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.updateSurface()
// Setup overlays
updateShowStatsOverlay()
updateSocOverlay()
@ -418,7 +715,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 +753,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,7 +769,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
if (it) startEmulation()
Log.info("[EmulationFragment] Driver interaction allowed: $it")
if (it && !NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
startEmulation()
}
}
driverViewModel.onLaunchGame()
val currentDriverState = driverViewModel.isInteractionAllowed.value
Log.info("[EmulationFragment] Checking initial driver state after onLaunchGame: $currentDriverState")
if (currentDriverState && !NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
Log.info("[EmulationFragment] Starting emulation immediately - driver already ready")
startEmulation()
}
}
@ -485,6 +793,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updateScreenLayout()
Log.info("[EmulationFragment] Calling emulationState.run() - surface will start emulation when available")
emulationState.run(emulationActivity!!.isActivityRecreated, programIndex)
}
}
@ -518,6 +827,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 +952,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 +966,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 +978,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 +996,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 +1093,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 +1260,19 @@ 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) {
Log.info("[EmulationFragment] Starting emulation")
emulationStarted = true
emulationState.newSurface(holder.surface)
} else {
Log.debug("[EmulationFragment] Emulation already started, updating surface")
emulationState.newSurface(holder.surface)
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
emulationState.clearSurface()
emulationStarted = false
}
private fun showOverlayOptions() {
@ -1096,22 +1445,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 +1492,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 +1513,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
lateinit var emulationThread: Thread
init {
// Starting state is stopped.
state = State.STOPPED
}
@ -1176,7 +1520,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 +1539,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 ->
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

@ -153,8 +153,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

@ -151,8 +151,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

@ -150,8 +150,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -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>

View file

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -116,8 +116,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

@ -151,8 +151,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

@ -151,8 +151,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

@ -150,8 +150,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