Archived
1
0
forked from MeloNX/MeloNX

android - update version

android - stop loading game if update fails

android - add refresh list button

android - fix update error return code

android - fix compose error with game list

android - add progress indicator for game list

android - fix game list loading

android - adjust virtual controller button positions. added stick sensitivity option

android - bump version

android - fix vulkan driver install
This commit is contained in:
Emmanuel Hansen 2024-07-15 16:16:08 +00:00
parent d19582b055
commit 1a94e37816
12 changed files with 326 additions and 113 deletions

View File

@ -11,8 +11,8 @@ android {
applicationId "org.ryujinx.android"
minSdk 30
targetSdk 34
versionCode 10032
versionName '1.0.32'
versionCode 10039
versionName '1.0.39'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
@ -193,19 +194,25 @@ class GameController(var activity: Activity) {
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
RyujinxNative.jnaInstance.inputSetStickAxis(
1,
ev.xAxis,
-ev.yAxis,
x,
-y,
this
)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
RyujinxNative.jnaInstance.inputSetStickAxis(
2,
ev.xAxis,
-ev.yAxis,
x,
-y,
this
)
}
@ -226,7 +233,8 @@ suspend fun <T> Flow<T>.safeCollect(
}
private fun generateConfig(isLeft: Boolean): GamePadConfig {
val distance = 0.05f
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
@ -240,9 +248,9 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
),
listOf(
SecondaryDialConfig.Cross(
9,
10,
3,
1.8f,
2.5f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
@ -256,9 +264,9 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.SingleButton(
0,
1f,
0.05f,
1,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
@ -274,7 +282,7 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
),
SecondaryDialConfig.DoubleButton(
2,
0.05f,
distance,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
@ -289,9 +297,9 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
SecondaryDialConfig.RotationProcessor()
),
SecondaryDialConfig.SingleButton(
8,
1f,
0.05f,
9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
@ -362,8 +370,8 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
SecondaryDialConfig.Stick(
7,
2,
3f,
0.05f,
2f,
distance,
GamePadButtonInputId.RightStick.ordinal,
GamePadButtonInputId.RightStickButton.ordinal,
null,
@ -373,8 +381,8 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
),
SecondaryDialConfig.SingleButton(
6,
1f,
0.05f,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
@ -390,7 +398,7 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
),
SecondaryDialConfig.DoubleButton(
3,
0.05f,
distance,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
@ -406,8 +414,8 @@ private fun generateConfig(isLeft: Boolean): GamePadConfig {
),
SecondaryDialConfig.SingleButton(
9,
1f,
0.05f,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",

View File

@ -23,6 +23,61 @@ class Icons {
companion object {
/// Icons exported from https://www.composables.com/icons
@Composable
fun circle(color: Color): ImageVector {
return remember {
ImageVector.Builder(
name = "circle",
defaultWidth = 40.0.dp,
defaultHeight = 40.0.dp,
viewportWidth = 40.0f,
viewportHeight = 40.0f
).apply {
path(
fill = SolidColor(color),
fillAlpha = 1f,
stroke = null,
strokeAlpha = 1f,
strokeLineWidth = 1.0f,
strokeLineCap = StrokeCap.Butt,
strokeLineJoin = StrokeJoin.Miter,
strokeLineMiter = 1f,
pathFillType = PathFillType.NonZero
) {
moveTo(20f, 36.375f)
quadToRelative(-3.375f, 0f, -6.375f, -1.292f)
quadToRelative(-3f, -1.291f, -5.208f, -3.521f)
quadToRelative(-2.209f, -2.229f, -3.5f, -5.208f)
quadTo(3.625f, 23.375f, 3.625f, 20f)
quadToRelative(0f, -3.417f, 1.292f, -6.396f)
quadToRelative(1.291f, -2.979f, 3.521f, -5.208f)
quadToRelative(2.229f, -2.229f, 5.208f, -3.5f)
reflectiveQuadTo(20f, 3.625f)
quadToRelative(3.417f, 0f, 6.396f, 1.292f)
quadToRelative(2.979f, 1.291f, 5.208f, 3.5f)
quadToRelative(2.229f, 2.208f, 3.5f, 5.187f)
reflectiveQuadTo(36.375f, 20f)
quadToRelative(0f, 3.375f, -1.292f, 6.375f)
quadToRelative(-1.291f, 3f, -3.5f, 5.208f)
quadToRelative(-2.208f, 2.209f, -5.187f, 3.5f)
quadToRelative(-2.979f, 1.292f, -6.396f, 1.292f)
close()
moveToRelative(0f, -2.625f)
quadToRelative(5.75f, 0f, 9.75f, -4.021f)
reflectiveQuadToRelative(4f, -9.729f)
quadToRelative(0f, -5.75f, -4f, -9.75f)
reflectiveQuadToRelative(-9.75f, -4f)
quadToRelative(-5.708f, 0f, -9.729f, 4f)
quadToRelative(-4.021f, 4f, -4.021f, 9.75f)
quadToRelative(0f, 5.708f, 4.021f, 9.729f)
quadTo(14.292f, 33.75f, 20f, 33.75f)
close()
moveTo(20f, 20f)
close()
}
}.build()
}
}
@Composable
fun listView(color: Color): ImageVector {
return remember {
ImageVector.Builder(

View File

@ -59,9 +59,14 @@ class GameModel(var file: DocumentFile, val context: Context) {
val uri = Uri.parse(vm.data?.selected)
val file = DocumentFile.fromSingleUri(context, uri)
if (file?.exists() == true) {
updateDescriptor = context.contentResolver.openFileDescriptor(file.uri, "rw")
try {
updateDescriptor =
context.contentResolver.openFileDescriptor(file.uri, "rw")
return updateDescriptor?.fd ?: -1
return updateDescriptor?.fd ?: -1
} catch (e: Exception) {
return -2
}
}
}
}

View File

@ -1,6 +1,8 @@
package org.ryujinx.android.viewmodels
import android.content.SharedPreferences
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
@ -8,6 +10,10 @@ import com.anggrayudi.storage.file.DocumentFileCompat
import com.anggrayudi.storage.file.DocumentFileType
import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.search
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.ryujinx.android.MainActivity
import java.util.Locale
import kotlin.concurrent.thread
@ -18,11 +24,11 @@ class HomeViewModel(
) {
private var shouldReload: Boolean = false
private var savedFolder: String = ""
private var isLoading: Boolean = false
private var loadedCache: MutableList<GameModel> = mutableListOf()
private var gameFolderPath: DocumentFile? = null
private var sharedPref: SharedPreferences? = null
val gameList: SnapshotStateList<GameModel> = SnapshotStateList()
val isLoading: MutableState<Boolean> = mutableStateOf(false)
init {
if (activity != null) {
@ -59,19 +65,21 @@ class HomeViewModel(
shouldReload = true
}
@OptIn(DelicateCoroutinesApi::class)
private fun reloadGameList() {
activity?.storageHelper ?: return
if (isLoading)
return
val folder = gameFolderPath ?: return
gameList.clear()
shouldReload = false
if (isLoading.value)
return
gameList.clear()
loadedCache.clear()
isLoading.value = true
isLoading = true
thread {
try {
loadedCache.clear()
for (file in folder.search(false, DocumentFileType.FILE)) {
if (file.extension == "xci" || file.extension == "nsp" || file.extension == "nro")
activity.let {
@ -79,14 +87,14 @@ class HomeViewModel(
if (item.titleId?.isNotEmpty() == true && item.titleName?.isNotEmpty() == true && item.titleName != "Unknown") {
loadedCache.add(item)
gameList.add(item)
}
}
}
isLoading = false
} finally {
isLoading = false
isLoading.value = false
GlobalScope.launch(Dispatchers.Main){
filter("")
}
}
}
}

View File

@ -66,14 +66,19 @@ class MainViewModel(val activity: MainActivity) {
firmwareVersion = RyujinxNative.jnaInstance.deviceGetInstalledFirmwareVersion()
}
fun loadGame(game: GameModel): Boolean {
fun loadGame(game: GameModel): Int {
val descriptor = game.open()
if (descriptor == 0)
return false
return 0
val update = game.openUpdate()
if(update == -2)
{
return -2
}
gameModel = game
isMiiEditorLaunched = false
@ -87,7 +92,7 @@ class MainViewModel(val activity: MainActivity) {
)
if (!success)
return false
return 0
val nativeHelpers = NativeHelpers.instance
val nativeInterop = NativeGraphicsInterop()
@ -141,7 +146,7 @@ class MainViewModel(val activity: MainActivity) {
driverHandle
)
if (!success)
return false
return 0
val semaphore = Semaphore(1, 0)
runBlocking {
@ -168,12 +173,12 @@ class MainViewModel(val activity: MainActivity) {
}
if (!success)
return false
return 0
success =
RyujinxNative.jnaInstance.deviceLoadDescriptor(descriptor, game.type.ordinal, update)
return success
return if (success) 1 else 0
}
fun loadMiiEditor(): Boolean {

View File

@ -19,6 +19,7 @@ class QuickSettings(val activity: Activity) {
var useSwitchLayout: Boolean
var enableMotion: Boolean
var enablePerformanceMode: Boolean
var controllerStickSensitivity: Float
// Logs
var enableDebugLogs: Boolean
@ -49,6 +50,7 @@ class QuickSettings(val activity: Activity) {
useSwitchLayout = sharedPref.getBoolean("useSwitchLayout", true)
enableMotion = sharedPref.getBoolean("enableMotion", true)
enablePerformanceMode = sharedPref.getBoolean("enablePerformanceMode", true)
controllerStickSensitivity = sharedPref.getFloat("controllerStickSensitivity", 1.0f)
enableDebugLogs = sharedPref.getBoolean("enableDebugLogs", false)
enableStubLogs = sharedPref.getBoolean("enableStubLogs", false)
@ -78,6 +80,7 @@ class QuickSettings(val activity: Activity) {
editor.putBoolean("useSwitchLayout", useSwitchLayout)
editor.putBoolean("enableMotion", enableMotion)
editor.putBoolean("enablePerformanceMode", enablePerformanceMode)
editor.putFloat("enablePerformanceMode", controllerStickSensitivity)
editor.putBoolean("enableDebugLogs", enableDebugLogs)
editor.putBoolean("enableStubLogs", enableStubLogs)

View File

@ -56,6 +56,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
useSwitchLayout: MutableState<Boolean>,
enableMotion: MutableState<Boolean>,
enablePerformanceMode: MutableState<Boolean>,
controllerStickSensitivity: MutableState<Float>,
enableDebugLogs: MutableState<Boolean>,
enableStubLogs: MutableState<Boolean>,
enableInfoLogs: MutableState<Boolean>,
@ -82,6 +83,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
useSwitchLayout.value = sharedPref.getBoolean("useSwitchLayout", true)
enableMotion.value = sharedPref.getBoolean("enableMotion", true)
enablePerformanceMode.value = sharedPref.getBoolean("enablePerformanceMode", false)
controllerStickSensitivity.value = sharedPref.getFloat("controllerStickSensitivity", 1.0f)
enableDebugLogs.value = sharedPref.getBoolean("enableDebugLogs", false)
enableStubLogs.value = sharedPref.getBoolean("enableStubLogs", false)
@ -109,6 +111,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
useSwitchLayout: MutableState<Boolean>,
enableMotion: MutableState<Boolean>,
enablePerformanceMode: MutableState<Boolean>,
controllerStickSensitivity: MutableState<Float>,
enableDebugLogs: MutableState<Boolean>,
enableStubLogs: MutableState<Boolean>,
enableInfoLogs: MutableState<Boolean>,
@ -135,6 +138,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
editor.putBoolean("useSwitchLayout", useSwitchLayout.value)
editor.putBoolean("enableMotion", enableMotion.value)
editor.putBoolean("enablePerformanceMode", enablePerformanceMode.value)
editor.putFloat("controllerStickSensitivity", controllerStickSensitivity.value)
editor.putBoolean("enableDebugLogs", enableDebugLogs.value)
editor.putBoolean("enableStubLogs", enableStubLogs.value)

View File

@ -19,8 +19,8 @@ class TitleUpdateViewModel(val titleId: String) {
private var basePath: String
private var updateJsonName = "updates.json"
private var storageHelper: SimpleStorageHelper
var currentPaths: MutableList<String> = mutableListOf()
var pathsState: SnapshotStateList<String>? = null
private var currentPaths: MutableList<String> = mutableListOf()
private var pathsState: SnapshotStateList<String>? = null
companion object {
const val UpdateRequestCode = 1002

View File

@ -2,13 +2,13 @@ package org.ryujinx.android.viewmodels
import androidx.compose.runtime.MutableState
import com.anggrayudi.storage.file.extension
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.openInputStream
import com.google.gson.Gson
import org.ryujinx.android.MainActivity
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
class VulkanDriverViewModel(val activity: MainActivity) {
var selected: String = ""
@ -105,39 +105,37 @@ class VulkanDriverViewModel(val activity: MainActivity) {
if (requestCode == DriverRequestCode) {
val file = files.firstOrNull()
file?.apply {
val path = file.getAbsolutePath(storage.context)
if (path.isNotEmpty()) {
val stream = file.openInputStream(storage.context)
stream?.apply {
val name = file.name?.removeSuffix("." + file.extension) ?: ""
val driverFolder = ensureDriverPath()
val extractionFolder = File(driverFolder.absolutePath + "/${name}")
extractionFolder.deleteRecursively()
extractionFolder.mkdirs()
ZipFile(path).use { zip ->
zip.entries().asSequence().forEach { entry ->
ZipInputStream(stream).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
val filePath =
extractionFolder.absolutePath + File.separator + entry.name
zip.getInputStream(entry).use { input ->
val filePath =
extractionFolder.absolutePath + File.separator + entry.name
if (!entry.isDirectory) {
File(filePath).delete()
val bos =
BufferedOutputStream(FileOutputStream(filePath))
val bytesIn = ByteArray(4096)
var read: Int
while (input.read(bytesIn)
.also { read = it } != -1
) {
bos.write(bytesIn, 0, read)
}
bos.close()
} else {
val dir = File(filePath)
dir.mkdir()
if (!entry.isDirectory) {
File(filePath).delete()
val bos =
BufferedOutputStream(FileOutputStream(filePath))
val bytesIn = ByteArray(4096)
var read: Int
while (zip.read(bytesIn)
.also { read = it } != -1
) {
bos.write(bytesIn, 0, read)
}
bos.close()
} else {
val dir = File(filePath)
dir.mkdir()
}
entry = zip.nextEntry
}
}
}

View File

@ -3,6 +3,8 @@ package org.ryujinx.android.views
import android.content.res.Resources
import android.graphics.BitmapFactory
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.basicMarquee
@ -32,15 +34,17 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
@ -61,8 +65,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
@ -99,6 +107,9 @@ class HomeViews {
val canClose = remember { mutableStateOf(true) }
val openDlcDialog = remember { mutableStateOf(false) }
var openAppBarExtra by remember { mutableStateOf(false) }
val showError = remember {
mutableStateOf("")
}
val selectedModel = remember {
mutableStateOf(viewModel.mainViewModel?.selected)
@ -110,6 +121,24 @@ class HomeViews {
mutableStateOf(true)
}
var isFabVisible by remember {
mutableStateOf(true)
}
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (available.y < -1) {
isFabVisible = false
}
if (available.y > 1) {
isFabVisible = true
}
return Offset.Zero
}
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
@ -170,6 +199,21 @@ class HomeViews {
) {
}
},
floatingActionButton = {
AnimatedVisibility(visible = isFabVisible,
enter = slideInVertically(initialOffsetY = { it * 2 }),
exit = slideOutVertically(targetOffsetY = { it * 2 })) {
FloatingActionButton(
onClick = {
viewModel.requestReload()
viewModel.ensureReloadIfNecessary()
},
shape = MaterialTheme.shapes.small
) {
Icon(Icons.Default.Refresh, contentDescription = "refresh")
}
}
}
) { contentPadding ->
@ -309,56 +353,75 @@ class HomeViews {
val list = remember {
viewModel.gameList
}
val isLoading = remember {
viewModel.isLoading
}
viewModel.filter(query.value)
if (!isPreview) {
var settings = QuickSettings(viewModel.activity!!)
if (settings.isGrid) {
val size =
GridImageSize / Resources.getSystem().displayMetrics.density
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = (size + 4).dp),
modifier = Modifier
.fillMaxSize()
.padding(4.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
items(list) {
it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(Locale.getDefault())
.contains(query.value))
)
GridGameItem(
it,
viewModel,
showAppActions,
showLoading,
selectedModel
)
}
}
if (isLoading.value) {
Box(modifier = Modifier.fillMaxSize())
{
CircularProgressIndicator(
modifier = Modifier
.width(64.dp)
.align(Alignment.Center),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant
)
}
} else {
LazyColumn(Modifier.fillMaxSize()) {
items(list) {
it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(
Locale.getDefault()
if (settings.isGrid) {
val size =
GridImageSize / Resources.getSystem().displayMetrics.density
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = (size + 4).dp),
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
.nestedScroll(nestedScrollConnection),
horizontalArrangement = Arrangement.SpaceEvenly
) {
items(list) {
it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(Locale.getDefault())
.contains(query.value))
)
.contains(query.value))
)
Box(modifier = Modifier.animateItemPlacement()) {
ListGameItem(
GridGameItem(
it,
viewModel,
showAppActions,
showLoading,
selectedModel,
showError
)
}
}
}
}
} else {
LazyColumn(Modifier.fillMaxSize()) {
items(list) {
it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(
Locale.getDefault()
)
.contains(query.value))
)
Box(modifier = Modifier.animateItemPlacement()) {
ListGameItem(
it,
viewModel,
showAppActions,
showLoading,
selectedModel,
showError
)
}
}
}
}
}
@ -443,11 +506,14 @@ class HomeViews {
showLoading.value = true
val success =
viewModel.mainViewModel.loadGame(viewModel.mainViewModel.selected!!)
if (success) {
if (success == 1) {
launchOnUiThread {
viewModel.mainViewModel.navigateToGame()
}
} else {
if (success == -2)
showError.value =
"Error loading update. Please re-add update file"
viewModel.mainViewModel.selected!!.close()
}
showLoading.value = false
@ -527,7 +593,8 @@ class HomeViews {
viewModel: HomeViewModel,
showAppActions: MutableState<Boolean>,
showLoading: MutableState<Boolean>,
selectedModel: MutableState<GameModel?>
selectedModel: MutableState<GameModel?>,
showError: MutableState<String>
) {
remember {
selectedModel
@ -555,11 +622,14 @@ class HomeViews {
showLoading.value = true
val success =
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success) {
if (success == 1) {
launchOnUiThread {
viewModel.mainViewModel?.navigateToGame()
}
} else {
if (success == -2)
showError.value =
"Error loading update. Please re-add update file"
gameModel.close()
}
showLoading.value = false
@ -618,7 +688,8 @@ class HomeViews {
viewModel: HomeViewModel,
showAppActions: MutableState<Boolean>,
showLoading: MutableState<Boolean>,
selectedModel: MutableState<GameModel?>
selectedModel: MutableState<GameModel?>,
showError: MutableState<String>
) {
remember {
selectedModel
@ -646,11 +717,14 @@ class HomeViews {
showLoading.value = true
val success =
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success) {
if (success == 1) {
launchOnUiThread {
viewModel.mainViewModel?.navigateToGame()
}
} else {
if (success == -2)
showError.value =
"Error loading update. Please re-add update file"
gameModel.close()
}
showLoading.value = false

View File

@ -15,6 +15,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
@ -24,6 +25,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
@ -31,16 +35,18 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Label
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
@ -125,6 +131,7 @@ class SettingViews {
val useSwitchLayout = remember { mutableStateOf(true) }
val enableMotion = remember { mutableStateOf(true) }
val enablePerformanceMode = remember { mutableStateOf(true) }
val controllerStickSensitivity = remember { mutableStateOf(1.0f) }
val enableDebugLogs = remember { mutableStateOf(true) }
val enableStubLogs = remember { mutableStateOf(true) }
@ -149,6 +156,7 @@ class SettingViews {
useSwitchLayout,
enableMotion,
enablePerformanceMode,
controllerStickSensitivity,
enableDebugLogs,
enableStubLogs,
enableInfoLogs,
@ -184,6 +192,7 @@ class SettingViews {
useSwitchLayout,
enableMotion,
enablePerformanceMode,
controllerStickSensitivity,
enableDebugLogs,
enableStubLogs,
enableInfoLogs,
@ -926,6 +935,49 @@ class SettingViews {
useSwitchLayout.value = !useSwitchLayout.value
})
}
val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Controller Stick Sensitivity",
modifier = Modifier.align(Alignment.CenterVertically)
)
Slider(modifier = Modifier.width(250.dp), value = controllerStickSensitivity.value, onValueChange = {
controllerStickSensitivity.value = it
}, valueRange = 0.1f..2f,
steps = 20,
interactionSource = interactionSource,
thumb = {
Label(
label = {
PlainTooltip(modifier = Modifier
.sizeIn(45.dp, 25.dp)
.wrapContentWidth()) {
Text("%.2f".format(controllerStickSensitivity.value))
}
},
interactionSource = interactionSource
) {
Icon(
imageVector = org.ryujinx.android.Icons.circle(
color = MaterialTheme.colorScheme.primary
),
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
tint = MaterialTheme.colorScheme.primary
)
}
}
)
}
}
}
ExpandableView(onCardArrowClick = { }, title = "Log") {
@ -1094,6 +1146,7 @@ class SettingViews {
useSwitchLayout,
enableMotion,
enablePerformanceMode,
controllerStickSensitivity,
enableDebugLogs,
enableStubLogs,
enableInfoLogs,