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:
parent
d19582b055
commit
1a94e37816
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user