forked from MeloNX/MeloNX
clean main ui, add option to import app data
This commit is contained in:
parent
34e52880da
commit
8e2eb5fd26
@ -99,6 +99,7 @@ dependencies {
|
|||||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
|
implementation 'net.lingala.zip4j:zip4j:2.11.5'
|
||||||
implementation("br.com.devsrsouza.compose.icons:css-gg:1.1.0")
|
implementation("br.com.devsrsouza.compose.icons:css-gg:1.1.0")
|
||||||
implementation "io.coil-kt:coil-compose:2.4.0"
|
implementation "io.coil-kt:coil-compose:2.4.0"
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
@ -7,9 +7,16 @@ import android.net.Uri
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.anggrayudi.storage.file.openInputStream
|
||||||
|
import net.lingala.zip4j.io.inputstream.ZipInputStream
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
class Helpers {
|
class Helpers {
|
||||||
companion object{
|
companion object {
|
||||||
fun getPath(context: Context, uri: Uri): String? {
|
fun getPath(context: Context, uri: Uri): String? {
|
||||||
|
|
||||||
// DocumentProvider
|
// DocumentProvider
|
||||||
@ -25,7 +32,10 @@ class Helpers {
|
|||||||
|
|
||||||
} else if (isDownloadsDocument(uri)) {
|
} else if (isDownloadsDocument(uri)) {
|
||||||
val id = DocumentsContract.getDocumentId(uri)
|
val id = DocumentsContract.getDocumentId(uri)
|
||||||
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
|
val contentUri = ContentUris.withAppendedId(
|
||||||
|
Uri.parse("content://downloads/public_downloads"),
|
||||||
|
java.lang.Long.valueOf(id)
|
||||||
|
)
|
||||||
return getDataColumn(context, contentUri, null, null)
|
return getDataColumn(context, contentUri, null, null)
|
||||||
} else if (isMediaDocument(uri)) {
|
} else if (isMediaDocument(uri)) {
|
||||||
val docId = DocumentsContract.getDocumentId(uri)
|
val docId = DocumentsContract.getDocumentId(uri)
|
||||||
@ -36,9 +46,11 @@ class Helpers {
|
|||||||
"image" -> {
|
"image" -> {
|
||||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
}
|
}
|
||||||
|
|
||||||
"video" -> {
|
"video" -> {
|
||||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
}
|
}
|
||||||
|
|
||||||
"audio" -> {
|
"audio" -> {
|
||||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||||
}
|
}
|
||||||
@ -55,12 +67,25 @@ class Helpers {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
|
private fun getDataColumn(
|
||||||
|
context: Context,
|
||||||
|
uri: Uri?,
|
||||||
|
selection: String?,
|
||||||
|
selectionArgs: Array<String>?
|
||||||
|
): String? {
|
||||||
var cursor: Cursor? = null
|
var cursor: Cursor? = null
|
||||||
val column = "_data"
|
val column = "_data"
|
||||||
val projection = arrayOf(column)
|
val projection = arrayOf(column)
|
||||||
try {
|
try {
|
||||||
cursor = uri?.let { context.contentResolver.query(it, projection, selection, selectionArgs,null) }
|
cursor = uri?.let {
|
||||||
|
context.contentResolver.query(
|
||||||
|
it,
|
||||||
|
projection,
|
||||||
|
selection,
|
||||||
|
selectionArgs,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
val column_index: Int = cursor.getColumnIndexOrThrow(column)
|
val column_index: Int = cursor.getColumnIndexOrThrow(column)
|
||||||
return cursor.getString(column_index)
|
return cursor.getString(column_index)
|
||||||
@ -82,5 +107,56 @@ class Helpers {
|
|||||||
private fun isMediaDocument(uri: Uri): Boolean {
|
private fun isMediaDocument(uri: Uri): Boolean {
|
||||||
return "com.android.providers.media.documents" == uri.authority
|
return "com.android.providers.media.documents" == uri.authority
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun importAppData(
|
||||||
|
file: DocumentFile,
|
||||||
|
isImporting: MutableState<Boolean>
|
||||||
|
) {
|
||||||
|
isImporting.value = true
|
||||||
|
try {
|
||||||
|
MainActivity.StorageHelper?.apply {
|
||||||
|
val stream = file.openInputStream(storage.context)
|
||||||
|
stream?.apply {
|
||||||
|
val folders = listOf("bis", "games", "profiles", "system")
|
||||||
|
for (f in folders) {
|
||||||
|
val dir = File(MainActivity.AppPath + "${File.separator}${f}")
|
||||||
|
if (dir.exists()) {
|
||||||
|
dir.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
dir.mkdirs()
|
||||||
|
}
|
||||||
|
ZipInputStream(stream).use { zip ->
|
||||||
|
var count = 0
|
||||||
|
while (true) {
|
||||||
|
val header = zip.nextEntry ?: break
|
||||||
|
if (!folders.any { header.fileName.startsWith(it) }) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val filePath =
|
||||||
|
MainActivity.AppPath + File.separator + header.fileName
|
||||||
|
|
||||||
|
if (!header.isDirectory) {
|
||||||
|
val bos = BufferedOutputStream(FileOutputStream(filePath))
|
||||||
|
val bytesIn = ByteArray(4096)
|
||||||
|
var read: Int = 0
|
||||||
|
while (zip.read(bytesIn).also { read = it } > 0) {
|
||||||
|
bos.write(bytesIn, 0, read)
|
||||||
|
}
|
||||||
|
bos.close()
|
||||||
|
} else {
|
||||||
|
val dir = File(filePath)
|
||||||
|
dir.mkdir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isImporting.value = false
|
||||||
|
RyujinxNative().deviceReloadFilesystem()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,112 @@ class Icons {
|
|||||||
companion object{
|
companion object{
|
||||||
/// Icons exported from https://www.composables.com/icons
|
/// Icons exported from https://www.composables.com/icons
|
||||||
@Composable
|
@Composable
|
||||||
|
fun playArrow(color: Color): ImageVector {
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "play_arrow",
|
||||||
|
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(15.542f, 30f)
|
||||||
|
quadToRelative(-0.667f, 0.458f, -1.334f, 0.062f)
|
||||||
|
quadToRelative(-0.666f, -0.395f, -0.666f, -1.187f)
|
||||||
|
verticalLineTo(10.917f)
|
||||||
|
quadToRelative(0f, -0.75f, 0.666f, -1.146f)
|
||||||
|
quadToRelative(0.667f, -0.396f, 1.334f, 0.062f)
|
||||||
|
lineToRelative(14.083f, 9f)
|
||||||
|
quadToRelative(0.583f, 0.375f, 0.583f, 1.084f)
|
||||||
|
quadToRelative(0f, 0.708f, -0.583f, 1.083f)
|
||||||
|
close()
|
||||||
|
moveToRelative(0.625f, -10.083f)
|
||||||
|
close()
|
||||||
|
moveToRelative(0f, 6.541f)
|
||||||
|
lineToRelative(10.291f, -6.541f)
|
||||||
|
lineToRelative(-10.291f, -6.542f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun folderOpen(color: Color): ImageVector {
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "folder_open",
|
||||||
|
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(6.25f, 33.125f)
|
||||||
|
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
|
||||||
|
quadToRelative(-0.771f, -0.791f, -0.771f, -1.875f)
|
||||||
|
verticalLineTo(9.667f)
|
||||||
|
quadToRelative(0f, -1.084f, 0.771f, -1.854f)
|
||||||
|
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
|
||||||
|
horizontalLineToRelative(10.042f)
|
||||||
|
quadToRelative(0.541f, 0f, 1.041f, 0.208f)
|
||||||
|
quadToRelative(0.5f, 0.208f, 0.834f, 0.583f)
|
||||||
|
lineToRelative(1.875f, 1.834f)
|
||||||
|
horizontalLineTo(33.75f)
|
||||||
|
quadToRelative(1.083f, 0f, 1.854f, 0.791f)
|
||||||
|
quadToRelative(0.771f, 0.792f, 0.771f, 1.834f)
|
||||||
|
horizontalLineTo(18.917f)
|
||||||
|
lineTo(16.25f, 9.667f)
|
||||||
|
horizontalLineToRelative(-10f)
|
||||||
|
verticalLineTo(30.25f)
|
||||||
|
lineToRelative(3.542f, -13.375f)
|
||||||
|
quadToRelative(0.25f, -0.875f, 0.979f, -1.396f)
|
||||||
|
quadToRelative(0.729f, -0.521f, 1.604f, -0.521f)
|
||||||
|
horizontalLineToRelative(23.25f)
|
||||||
|
quadToRelative(1.292f, 0f, 2.104f, 1.021f)
|
||||||
|
quadToRelative(0.813f, 1.021f, 0.438f, 2.271f)
|
||||||
|
lineToRelative(-3.459f, 12.833f)
|
||||||
|
quadToRelative(-0.291f, 1f, -1f, 1.521f)
|
||||||
|
quadToRelative(-0.708f, 0.521f, -1.75f, 0.521f)
|
||||||
|
close()
|
||||||
|
moveToRelative(2.708f, -2.667f)
|
||||||
|
horizontalLineToRelative(23.167f)
|
||||||
|
lineToRelative(3.417f, -12.875f)
|
||||||
|
horizontalLineTo(12.333f)
|
||||||
|
close()
|
||||||
|
moveToRelative(0f, 0f)
|
||||||
|
lineToRelative(3.375f, -12.875f)
|
||||||
|
lineToRelative(-3.375f, 12.875f)
|
||||||
|
close()
|
||||||
|
moveToRelative(-2.708f, -15.5f)
|
||||||
|
verticalLineTo(9.667f)
|
||||||
|
verticalLineToRelative(5.291f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
fun download(): ImageVector {
|
fun download(): ImageVector {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
val primaryColor = MaterialTheme.colorScheme.primary
|
||||||
return remember {
|
return remember {
|
||||||
|
@ -38,6 +38,7 @@ class RyujinxNative {
|
|||||||
external fun graphicsRendererSetSize(width: Int, height: Int)
|
external fun graphicsRendererSetSize(width: Int, height: Int)
|
||||||
external fun graphicsRendererSetVsync(enabled: Boolean)
|
external fun graphicsRendererSetVsync(enabled: Boolean)
|
||||||
external fun graphicsRendererRunLoop()
|
external fun graphicsRendererRunLoop()
|
||||||
|
external fun deviceReloadFilesystem()
|
||||||
external fun inputInitialize(width: Int, height: Int)
|
external fun inputInitialize(width: Int, height: Int)
|
||||||
external fun inputSetClientSize(width: Int, height: Int)
|
external fun inputSetClientSize(width: Int, height: Int)
|
||||||
external fun inputSetTouchPoint(x: Int, y: Int)
|
external fun inputSetTouchPoint(x: Int, y: Int)
|
||||||
|
@ -109,6 +109,6 @@ class HomeViewModel(
|
|||||||
|
|
||||||
fun setViewList(list: SnapshotStateList<GameModel>) {
|
fun setViewList(list: SnapshotStateList<GameModel>) {
|
||||||
gameList = list
|
gameList = list
|
||||||
applyFilter()
|
reloadGameList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.ryujinx.android.views
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.view.Gravity
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -17,32 +16,30 @@ import androidx.compose.foundation.layout.wrapContentHeight
|
|||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.BottomAppBar
|
||||||
|
import androidx.compose.material3.BottomAppBarDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.DockedSearchBar
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FabPosition
|
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.FloatingActionButtonDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SearchBar
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
@ -51,13 +48,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogWindowProvider
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
@ -76,125 +70,14 @@ class HomeViews {
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainTopBar(
|
fun Home(
|
||||||
navController: NavHostController,
|
viewModel: HomeViewModel = HomeViewModel(),
|
||||||
query: MutableState<String>,
|
navController: NavHostController? = null
|
||||||
refresh: MutableState<Boolean>
|
|
||||||
) {
|
) {
|
||||||
val topBarSize = remember {
|
val showAppActions = remember { mutableStateOf(false) }
|
||||||
mutableStateOf(0)
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
val showOptionsPopup = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.zIndex(1f)
|
|
||||||
.padding(top = 8.dp)
|
|
||||||
.onSizeChanged {
|
|
||||||
topBarSize.value = it.height
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
DockedSearchBar(
|
|
||||||
shape = SearchBarDefaults.inputFieldShape,
|
|
||||||
query = query.value,
|
|
||||||
onQueryChange = {
|
|
||||||
query.value = it
|
|
||||||
},
|
|
||||||
onSearch = {},
|
|
||||||
active = false,
|
|
||||||
onActiveChange = {},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Search,
|
|
||||||
contentDescription = "Search Games"
|
|
||||||
)
|
|
||||||
},
|
|
||||||
placeholder = {
|
|
||||||
Text(text = "Search Games")
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
refresh.value = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Refresh,
|
|
||||||
contentDescription = "Refresh"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
showOptionsPopup.value = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.MoreVert,
|
|
||||||
contentDescription = "More"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Box {
|
|
||||||
if (showOptionsPopup.value) {
|
|
||||||
AlertDialog(
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
top = (topBarSize.value / Resources.getSystem().displayMetrics.density + 10).dp,
|
|
||||||
start = 16.dp, end = 16.dp
|
|
||||||
),
|
|
||||||
onDismissRequest = {
|
|
||||||
showOptionsPopup.value = false
|
|
||||||
}) {
|
|
||||||
val dialogWindowProvider =
|
|
||||||
LocalView.current.parent as DialogWindowProvider
|
|
||||||
dialogWindowProvider.window.setGravity(Gravity.TOP)
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.padding(16.dp),
|
|
||||||
shape = MaterialTheme.shapes.large,
|
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
navController.navigate("settings")
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.Start),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Settings,
|
|
||||||
contentDescription = "Settings"
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "Settings", modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.align(Alignment.CenterVertically)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun Home(viewModel: HomeViewModel = HomeViewModel(), navController: NavHostController? = null) {
|
|
||||||
val sheetState = rememberModalBottomSheetState()
|
|
||||||
val showBottomSheet = remember { mutableStateOf(false) }
|
|
||||||
val showLoading = remember { mutableStateOf(false) }
|
val showLoading = remember { mutableStateOf(false) }
|
||||||
|
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
||||||
|
val openDlcDialog = remember { mutableStateOf(false) }
|
||||||
val query = remember {
|
val query = remember {
|
||||||
mutableStateOf("")
|
mutableStateOf("")
|
||||||
}
|
}
|
||||||
@ -204,23 +87,153 @@ class HomeViews {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
navController?.apply {
|
TopAppBar(
|
||||||
MainTopBar(navController, query, refresh)
|
modifier = Modifier
|
||||||
}
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp),
|
||||||
|
title = {
|
||||||
|
SearchBar(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = SearchBarDefaults.inputFieldShape,
|
||||||
|
query = query.value,
|
||||||
|
onQueryChange = {
|
||||||
|
query.value = it
|
||||||
|
},
|
||||||
|
onSearch = {},
|
||||||
|
active = false,
|
||||||
|
onActiveChange = {},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Search,
|
||||||
|
contentDescription = "Search Games"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = "Ryujinx")
|
||||||
|
}
|
||||||
|
) { }
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Person,
|
||||||
|
contentDescription = "Run"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Settings,
|
||||||
|
contentDescription = "Settings"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
floatingActionButtonPosition = FabPosition.End,
|
bottomBar = {
|
||||||
floatingActionButton = {
|
BottomAppBar(actions = {
|
||||||
FloatingActionButton(onClick = {
|
if (showAppActions.value) {
|
||||||
viewModel.openGameFolder()
|
IconButton(onClick = {
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
org.ryujinx.android.Icons.playArrow(MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = "Run"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val showAppMenu = remember { mutableStateOf(false) }
|
||||||
|
IconButton(onClick = {
|
||||||
|
showAppMenu.value = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Menu,
|
||||||
|
contentDescription = "Menu"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true) {
|
||||||
|
AlertDialog(onDismissRequest = {
|
||||||
|
showAppMenu.value = false
|
||||||
|
}) {
|
||||||
|
Surface(shape = MaterialTheme.shapes.medium, color = Color.Black) {
|
||||||
|
Row {
|
||||||
|
IconButton(onClick = {
|
||||||
|
openTitleUpdateDialog.value = true
|
||||||
|
}) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.app_update),
|
||||||
|
contentDescription = "Updates",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(20.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Updates",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconButton(onClick = {
|
||||||
|
openDlcDialog.value = true
|
||||||
|
}) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = org.ryujinx.android.Icons.download(),
|
||||||
|
contentDescription = "Dlc",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(20.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "DLC",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
shape = CircleShape) {
|
floatingActionButton = {
|
||||||
Icon(
|
FloatingActionButton(
|
||||||
Icons.Filled.Add,
|
onClick = {
|
||||||
contentDescription = "Options"
|
viewModel.openGameFolder()
|
||||||
)
|
},
|
||||||
}
|
containerColor = BottomAppBarDefaults.bottomAppBarFabColor,
|
||||||
|
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
org.ryujinx.android.Icons.folderOpen(MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = "Open Folder"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
Box(modifier = Modifier.padding(contentPadding)) {
|
Box(modifier = Modifier.padding(contentPadding)) {
|
||||||
val list = remember {
|
val list = remember {
|
||||||
@ -228,137 +241,93 @@ class HomeViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(refresh.value) {
|
if (refresh.value) {
|
||||||
viewModel.setViewList(list)
|
viewModel.setViewList(list)
|
||||||
refresh.value = false
|
refresh.value = false
|
||||||
}
|
}
|
||||||
|
val selectedModel = remember {
|
||||||
|
mutableStateOf(viewModel.mainViewModel?.selected)
|
||||||
|
}
|
||||||
LazyColumn(Modifier.fillMaxSize()) {
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
items(list) {
|
items(list) {
|
||||||
it.titleName?.apply {
|
it.titleName?.apply {
|
||||||
if (this.isNotEmpty() && (query.value.trim().isEmpty() || this.lowercase(
|
if (this.isNotEmpty() && (query.value.trim()
|
||||||
|
.isEmpty() || this.lowercase(
|
||||||
Locale.getDefault()
|
Locale.getDefault()
|
||||||
)
|
)
|
||||||
.contains(query.value)))
|
.contains(query.value))
|
||||||
GameItem(it, viewModel, showBottomSheet, showLoading)
|
)
|
||||||
|
GameItem(
|
||||||
|
it,
|
||||||
|
viewModel,
|
||||||
|
showAppActions,
|
||||||
|
showLoading,
|
||||||
|
selectedModel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(showLoading.value){
|
if (showLoading.value) {
|
||||||
AlertDialog(onDismissRequest = { }) {
|
AlertDialog(onDismissRequest = { }) {
|
||||||
Card(modifier = Modifier
|
Card(
|
||||||
.padding(16.dp)
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
|
||||||
shape = MaterialTheme.shapes.medium) {
|
|
||||||
Column(modifier = Modifier
|
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
.fillMaxWidth()) {
|
.fillMaxWidth(),
|
||||||
Text(text = "Loading")
|
shape = MaterialTheme.shapes.medium
|
||||||
LinearProgressIndicator(modifier = Modifier
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 16.dp))
|
) {
|
||||||
|
Text(text = "Loading")
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(showBottomSheet.value) {
|
|
||||||
ModalBottomSheet(onDismissRequest = {
|
|
||||||
showBottomSheet.value = false
|
|
||||||
},
|
|
||||||
sheetState = sheetState) {
|
|
||||||
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
|
||||||
val openDlcDialog = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if(openTitleUpdateDialog.value) {
|
if (openTitleUpdateDialog.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
AlertDialog(onDismissRequest = {
|
||||||
openTitleUpdateDialog.value = false
|
openTitleUpdateDialog.value = false
|
||||||
}) {
|
}) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.wrapContentWidth()
|
.wrapContentWidth()
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight(),
|
||||||
shape = MaterialTheme.shapes.large,
|
shape = MaterialTheme.shapes.large,
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
) {
|
) {
|
||||||
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog)
|
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog)
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if(openDlcDialog.value) {
|
|
||||||
AlertDialog(onDismissRequest = {
|
|
||||||
openDlcDialog.value = false
|
|
||||||
}) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.wrapContentWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
shape = MaterialTheme.shapes.large,
|
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
|
||||||
) {
|
|
||||||
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
|
||||||
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
|
||||||
DlcViews.Main(titleId, name, openDlcDialog)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Surface(color = MaterialTheme.colorScheme.surface,
|
}
|
||||||
modifier = Modifier.padding(16.dp)) {
|
if (openDlcDialog.value) {
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
AlertDialog(onDismissRequest = {
|
||||||
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
openDlcDialog.value = false
|
||||||
Card(
|
}) {
|
||||||
modifier = Modifier.padding(8.dp),
|
Surface(
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
openTitleUpdateDialog.value = true
|
.wrapContentWidth()
|
||||||
}
|
.wrapContentHeight(),
|
||||||
) {
|
shape = MaterialTheme.shapes.large,
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
Icon(
|
) {
|
||||||
painter = painterResource(R.drawable.app_update),
|
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||||
contentDescription = "Game Updates",
|
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||||
tint = Color.Green,
|
DlcViews.Main(titleId, name, openDlcDialog)
|
||||||
modifier = Modifier
|
|
||||||
.width(48.dp)
|
|
||||||
.height(48.dp)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(text = "Game Updates",
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
onClick = {
|
|
||||||
openDlcDialog.value = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Icon(
|
|
||||||
imageVector = org.ryujinx.android.Icons.download(),
|
|
||||||
contentDescription = "Game Dlc",
|
|
||||||
tint = Color.Green,
|
|
||||||
modifier = Modifier
|
|
||||||
.width(48.dp)
|
|
||||||
.height(48.dp)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
Text(text = "Game DLC",
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,16 +338,31 @@ class HomeViews {
|
|||||||
fun GameItem(
|
fun GameItem(
|
||||||
gameModel: GameModel,
|
gameModel: GameModel,
|
||||||
viewModel: HomeViewModel,
|
viewModel: HomeViewModel,
|
||||||
showSheet: MutableState<Boolean>,
|
showAppActions: MutableState<Boolean>,
|
||||||
showLoading: MutableState<Boolean>
|
showLoading: MutableState<Boolean>,
|
||||||
|
selectedModel: MutableState<GameModel?>
|
||||||
) {
|
) {
|
||||||
Surface(shape = MaterialTheme.shapes.medium,
|
remember {
|
||||||
|
selectedModel
|
||||||
|
}
|
||||||
|
val color =
|
||||||
|
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
color = color,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
|
if (viewModel.mainViewModel?.selected != null) {
|
||||||
|
showAppActions.value = false
|
||||||
|
viewModel.mainViewModel?.apply {
|
||||||
|
selected = null
|
||||||
|
}
|
||||||
|
selectedModel.value = null
|
||||||
|
} else if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000") {
|
||||||
thread {
|
thread {
|
||||||
showLoading.value = true
|
showLoading.value = true
|
||||||
val success =
|
val success =
|
||||||
@ -396,35 +380,40 @@ class HomeViews {
|
|||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
viewModel.mainViewModel?.selected = gameModel
|
viewModel.mainViewModel?.selected = gameModel
|
||||||
showSheet.value = true
|
showAppActions.value = true
|
||||||
})) {
|
selectedModel.value = gameModel
|
||||||
Row(modifier = Modifier
|
})
|
||||||
.fillMaxWidth()
|
) {
|
||||||
.padding(8.dp),
|
Row(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween) {
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
Row {
|
Row {
|
||||||
if(!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000")
|
if (!gameModel.titleId.isNullOrEmpty() && gameModel.titleId != "0000000000000000") {
|
||||||
{
|
val iconSource =
|
||||||
val iconSource = MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
|
MainActivity.AppPath + "/iconCache/" + gameModel.iconCache
|
||||||
val imageFile = File(iconSource)
|
val imageFile = File(iconSource)
|
||||||
if(imageFile.exists()) {
|
if (imageFile.exists()) {
|
||||||
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
val size = ImageSize / Resources.getSystem().displayMetrics.density
|
||||||
AsyncImage(model = imageFile,
|
AsyncImage(
|
||||||
|
model = imageFile,
|
||||||
contentDescription = gameModel.titleName + " icon",
|
contentDescription = gameModel.titleName + " icon",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 8.dp)
|
.padding(end = 8.dp)
|
||||||
.width(size.roundToInt().dp)
|
.width(size.roundToInt().dp)
|
||||||
.height(size.roundToInt().dp))
|
.height(size.roundToInt().dp)
|
||||||
}
|
)
|
||||||
else NotAvailableIcon()
|
} else NotAvailableIcon()
|
||||||
} else NotAvailableIcon()
|
} else NotAvailableIcon()
|
||||||
Column{
|
Column {
|
||||||
Text(text = gameModel.titleName ?: "")
|
Text(text = gameModel.titleName ?: "")
|
||||||
Text(text = gameModel.developer ?: "")
|
Text(text = gameModel.developer ?: "")
|
||||||
Text(text = gameModel.titleId ?: "")
|
Text(text = gameModel.titleId ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column{
|
Column {
|
||||||
Text(text = gameModel.version ?: "")
|
Text(text = gameModel.version ?: "")
|
||||||
Text(text = String.format("%.3f", gameModel.fileSize))
|
Text(text = String.format("%.3f", gameModel.fileSize))
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ package org.ryujinx.android.views
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
||||||
import androidx.compose.animation.core.MutableTransitionState
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
import androidx.compose.animation.core.animateDp
|
|
||||||
import androidx.compose.animation.core.animateFloat
|
import androidx.compose.animation.core.animateFloat
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.updateTransition
|
||||||
@ -33,6 +31,7 @@ import androidx.compose.material3.Card
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@ -51,12 +50,18 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.anggrayudi.storage.file.extension
|
||||||
|
import org.ryujinx.android.Helpers
|
||||||
|
import org.ryujinx.android.MainActivity
|
||||||
import org.ryujinx.android.viewmodels.SettingsViewModel
|
import org.ryujinx.android.viewmodels.SettingsViewModel
|
||||||
import org.ryujinx.android.viewmodels.VulkanDriverViewModel
|
import org.ryujinx.android.viewmodels.VulkanDriverViewModel
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class SettingViews {
|
class SettingViews {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXPANSTION_TRANSITION_DURATION = 450
|
const val EXPANSTION_TRANSITION_DURATION = 450
|
||||||
|
const val IMPORT_CODE = 12341
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -227,6 +232,118 @@ class SettingViews {
|
|||||||
ignoreMissingServices.value = !ignoreMissingServices.value
|
ignoreMissingServices.value = !ignoreMissingServices.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
val isImporting = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
val showImportWarning = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
val showImportCompletion = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
var importFile = remember {
|
||||||
|
mutableStateOf<DocumentFile?>(null)
|
||||||
|
}
|
||||||
|
Button(onClick = {
|
||||||
|
val storage = MainActivity.StorageHelper
|
||||||
|
storage?.apply {
|
||||||
|
val s = this.storage
|
||||||
|
val callBack = this.onFileSelected
|
||||||
|
onFileSelected = { requestCode, files ->
|
||||||
|
run {
|
||||||
|
onFileSelected = callBack
|
||||||
|
if (requestCode == IMPORT_CODE) {
|
||||||
|
val file = files.firstOrNull()
|
||||||
|
file?.apply {
|
||||||
|
if (this.extension == "zip") {
|
||||||
|
importFile.value = this
|
||||||
|
showImportWarning.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
openFilePicker(
|
||||||
|
IMPORT_CODE,
|
||||||
|
filterMimeTypes = arrayOf("application/zip")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(text = "Import App Data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showImportWarning.value) {
|
||||||
|
AlertDialog(onDismissRequest = {
|
||||||
|
showImportWarning.value = false
|
||||||
|
importFile.value = null
|
||||||
|
}) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = "Importing app data will delete your current profile. Do you still want to continue?")
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
val file = importFile.value
|
||||||
|
showImportWarning.value = false
|
||||||
|
importFile.value = null
|
||||||
|
file?.apply {
|
||||||
|
thread {
|
||||||
|
Helpers.importAppData(this, isImporting)
|
||||||
|
showImportCompletion.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||||
|
Text(text = "Yes")
|
||||||
|
}
|
||||||
|
Button(onClick = {
|
||||||
|
showImportWarning.value = false
|
||||||
|
importFile.value = null
|
||||||
|
}, modifier = Modifier.padding(horizontal = 8.dp)) {
|
||||||
|
Text(text = "No")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showImportCompletion.value) {
|
||||||
|
AlertDialog(onDismissRequest = {
|
||||||
|
showImportCompletion.value = false
|
||||||
|
importFile.value = null
|
||||||
|
}) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Text(modifier = Modifier
|
||||||
|
.padding(24.dp),
|
||||||
|
text = "App Data import completed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isImporting.value) {
|
||||||
|
Text(text = "Importing Files")
|
||||||
|
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExpandableView(onCardArrowClick = { }, title = "Graphics") {
|
ExpandableView(onCardArrowClick = { }, title = "Graphics") {
|
||||||
@ -257,14 +374,14 @@ class SettingViews {
|
|||||||
text = "Resolution Scale",
|
text = "Resolution Scale",
|
||||||
modifier = Modifier.align(Alignment.CenterVertically)
|
modifier = Modifier.align(Alignment.CenterVertically)
|
||||||
)
|
)
|
||||||
Text(text = resScale.value.toString() +"x")
|
Text(text = resScale.value.toString() + "x")
|
||||||
}
|
}
|
||||||
Slider(value = resScale.value,
|
Slider(value = resScale.value,
|
||||||
valueRange = 0.5f..4f,
|
valueRange = 0.5f..4f,
|
||||||
steps = 6,
|
steps = 6,
|
||||||
onValueChange = { it ->
|
onValueChange = { it ->
|
||||||
resScale.value = it
|
resScale.value = it
|
||||||
} )
|
})
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -276,9 +393,12 @@ class SettingViews {
|
|||||||
text = "Enable Texture Recompression",
|
text = "Enable Texture Recompression",
|
||||||
modifier = Modifier.align(Alignment.CenterVertically)
|
modifier = Modifier.align(Alignment.CenterVertically)
|
||||||
)
|
)
|
||||||
Switch(checked = enableTextureRecompression.value, onCheckedChange = {
|
Switch(
|
||||||
enableTextureRecompression.value = !enableTextureRecompression.value
|
checked = enableTextureRecompression.value,
|
||||||
})
|
onCheckedChange = {
|
||||||
|
enableTextureRecompression.value =
|
||||||
|
!enableTextureRecompression.value
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -290,7 +410,8 @@ class SettingViews {
|
|||||||
var isDriverSelectorOpen = remember {
|
var isDriverSelectorOpen = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
var driverViewModel = VulkanDriverViewModel(settingsViewModel.activity)
|
var driverViewModel =
|
||||||
|
VulkanDriverViewModel(settingsViewModel.activity)
|
||||||
var isChanged = remember {
|
var isChanged = remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
@ -302,16 +423,16 @@ class SettingViews {
|
|||||||
mutableStateOf(0)
|
mutableStateOf(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(refresh.value) {
|
if (refresh.value) {
|
||||||
isChanged.value = true
|
isChanged.value = true
|
||||||
refresh.value = false
|
refresh.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isDriverSelectorOpen.value){
|
if (isDriverSelectorOpen.value) {
|
||||||
AlertDialog(onDismissRequest = {
|
AlertDialog(onDismissRequest = {
|
||||||
isDriverSelectorOpen.value = false
|
isDriverSelectorOpen.value = false
|
||||||
|
|
||||||
if(isChanged.value){
|
if (isChanged.value) {
|
||||||
driverViewModel.saveSelected()
|
driverViewModel.saveSelected()
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@ -329,11 +450,15 @@ class SettingViews {
|
|||||||
isChanged.value = true
|
isChanged.value = true
|
||||||
}
|
}
|
||||||
Column {
|
Column {
|
||||||
Column (modifier = Modifier
|
Column(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.height(300.dp)) {
|
.fillMaxWidth()
|
||||||
|
.height(300.dp)
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
@ -359,7 +484,9 @@ class SettingViews {
|
|||||||
for (driver in drivers) {
|
for (driver in drivers) {
|
||||||
var ind = driverIndex
|
var ind = driverIndex
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
@ -378,15 +505,21 @@ class SettingViews {
|
|||||||
driverViewModel.selected =
|
driverViewModel.selected =
|
||||||
driver.driverPath
|
driver.driverPath
|
||||||
}) {
|
}) {
|
||||||
Text(text = driver.libraryName,
|
Text(
|
||||||
|
text = driver.libraryName,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth())
|
.fillMaxWidth()
|
||||||
Text(text = driver.driverVersion,
|
)
|
||||||
|
Text(
|
||||||
|
text = driver.driverVersion,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth())
|
.fillMaxWidth()
|
||||||
Text(text = driver.description,
|
)
|
||||||
|
Text(
|
||||||
|
text = driver.description,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth())
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,7 +558,7 @@ class SettingViews {
|
|||||||
isDriverSelectorOpen.value = !isDriverSelectorOpen.value
|
isDriverSelectorOpen.value = !isDriverSelectorOpen.value
|
||||||
},
|
},
|
||||||
modifier = Modifier.align(Alignment.CenterVertically)
|
modifier = Modifier.align(Alignment.CenterVertically)
|
||||||
){
|
) {
|
||||||
Text(text = "Drivers")
|
Text(text = "Drivers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,24 +618,6 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val transition = updateTransition(transitionState, label = "transition")
|
val transition = updateTransition(transitionState, label = "transition")
|
||||||
val cardPaddingHorizontal by transition.animateDp({
|
|
||||||
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
|
|
||||||
}, label = "paddingTransition") {
|
|
||||||
if (mutableExpanded.value) 48.dp else 24.dp
|
|
||||||
}
|
|
||||||
val cardElevation by transition.animateDp({
|
|
||||||
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
|
|
||||||
}, label = "elevationTransition") {
|
|
||||||
if (mutableExpanded.value) 24.dp else 4.dp
|
|
||||||
}
|
|
||||||
val cardRoundedCorners by transition.animateDp({
|
|
||||||
tween(
|
|
||||||
durationMillis = EXPANSTION_TRANSITION_DURATION,
|
|
||||||
easing = FastOutSlowInEasing
|
|
||||||
)
|
|
||||||
}, label = "cornersTransition") {
|
|
||||||
if (mutableExpanded.value) 0.dp else 16.dp
|
|
||||||
}
|
|
||||||
val arrowRotationDegree by transition.animateFloat({
|
val arrowRotationDegree by transition.animateFloat({
|
||||||
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
|
tween(durationMillis = EXPANSTION_TRANSITION_DURATION)
|
||||||
}, label = "rotationDegreeTransition") {
|
}, label = "rotationDegreeTransition") {
|
||||||
@ -514,7 +629,7 @@ class SettingViews {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(
|
||||||
horizontal = cardPaddingHorizontal,
|
horizontal = 24.dp,
|
||||||
vertical = 8.dp
|
vertical = 8.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@ -600,4 +715,4 @@ class SettingViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user