forked from MeloNX/MeloNX
android - improve game update selection
This commit is contained in:
parent
999fae7699
commit
0ed7220bb2
@ -41,6 +41,9 @@ namespace LibRyujinx
|
||||
[DllImport("libryujinxjni")]
|
||||
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
||||
|
||||
[DllImport("libryujinxjni")]
|
||||
private extern static void pushString(string ch);
|
||||
|
||||
[DllImport("libryujinxjni")]
|
||||
internal extern static void setRenderingThread();
|
||||
|
||||
@ -511,11 +514,11 @@ namespace LibRyujinx
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")]
|
||||
public static JStringLocalRef JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||
public static void JniGetOpenedUser(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||
{
|
||||
var userId = GetOpenedUser();
|
||||
|
||||
return CreateString(jEnv, userId);
|
||||
pushString(userId);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]
|
||||
|
@ -728,6 +728,7 @@ namespace LibRyujinx
|
||||
{
|
||||
VirtualFileSystem.ReloadKeySet();
|
||||
ContentManager = new ContentManager(VirtualFileSystem);
|
||||
AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient);
|
||||
}
|
||||
|
||||
internal void DisposeContext()
|
||||
|
@ -45,5 +45,6 @@ long _currentRenderingThreadId = 0;
|
||||
JavaVM* _vm = nullptr;
|
||||
jobject _mainActivity = nullptr;
|
||||
jclass _mainActivityClass = nullptr;
|
||||
std::string _currentString = "";
|
||||
|
||||
#endif //RYUJINXNATIVE_RYUIJNX_H
|
||||
|
@ -311,3 +311,25 @@ JNIEXPORT jstring JNICALL
|
||||
Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz) {
|
||||
return createStringFromStdString(env, progressInfo);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_ryujinx_android_NativeHelpers_popStringJava(JNIEnv *env, jobject thiz) {
|
||||
return createStringFromStdString(env, _currentString);
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_ryujinx_android_NativeHelpers_pushStringJava(JNIEnv *env, jobject thiz, jstring string) {
|
||||
_currentString = getStringPointer(env, string);
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
void pushString(char* str){
|
||||
_currentString = str;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
const char* popString(){
|
||||
return _currentString.c_str();
|
||||
}
|
||||
|
@ -354,6 +354,7 @@ class GameActivity : BaseActivity() {
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Button(onClick = {
|
||||
showBackNotice.value = false
|
||||
mainViewModel.closeGame()
|
||||
setFullScreen(false)
|
||||
finishActivity(0)
|
||||
|
@ -9,7 +9,13 @@ import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.anggrayudi.storage.callback.FileCallback
|
||||
import com.anggrayudi.storage.file.copyFileTo
|
||||
import com.anggrayudi.storage.file.openInputStream
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import net.lingala.zip4j.io.inputstream.ZipInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
@ -66,6 +72,61 @@ class Helpers {
|
||||
}
|
||||
return null
|
||||
}
|
||||
fun copyToData(
|
||||
file: DocumentFile, path: String, storageHelper: SimpleStorageHelper,
|
||||
isCopying: MutableState<Boolean>,
|
||||
copyProgress: MutableState<Float>,
|
||||
currentProgressName: MutableState<String>,
|
||||
finish: () -> Unit
|
||||
) {
|
||||
var callback: FileCallback? = object : FileCallback() {
|
||||
override fun onFailed(errorCode: FileCallback.ErrorCode) {
|
||||
super.onFailed(errorCode)
|
||||
File(path).delete()
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onStart(file: Any, workerThread: Thread): Long {
|
||||
copyProgress.value = 0f
|
||||
|
||||
(file as DocumentFile)?.apply {
|
||||
currentProgressName.value = "Copying ${file.name}"
|
||||
}
|
||||
return super.onStart(file, workerThread)
|
||||
}
|
||||
|
||||
override fun onReport(report: Report) {
|
||||
super.onReport(report)
|
||||
|
||||
if(!isCopying.value) {
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
|
||||
copyProgress.value = report.progress / 100f
|
||||
}
|
||||
|
||||
override fun onCompleted(result: Any) {
|
||||
super.onCompleted(result)
|
||||
isCopying.value = false
|
||||
finish()
|
||||
}
|
||||
}
|
||||
val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
isCopying.value = true
|
||||
file.apply {
|
||||
if (!File(path + "/${file.name}").exists()) {
|
||||
val f = this
|
||||
ioScope.launch {
|
||||
f.copyFileTo(
|
||||
storageHelper.storage.context,
|
||||
File(path),
|
||||
callback = callback!!
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDataColumn(
|
||||
context: Context,
|
||||
|
@ -28,4 +28,6 @@ class NativeHelpers {
|
||||
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
|
||||
external fun getProgressInfo() : String
|
||||
external fun getProgressValue() : Float
|
||||
external fun pushStringJava(string: String)
|
||||
external fun popStringJava() : String
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ class RyujinxNative {
|
||||
external fun deviceSignalEmulationClose()
|
||||
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
|
||||
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<String>
|
||||
external fun userGetOpenedUser() : String
|
||||
external fun userGetOpenedUser()
|
||||
external fun userGetUserPicture(userId: String) : String
|
||||
external fun userSetUserPicture(userId: String, picture: String)
|
||||
external fun userGetUserName(userId: String) : String
|
||||
|
@ -68,7 +68,7 @@ class HomeViewModel(
|
||||
)
|
||||
}
|
||||
|
||||
fun reloadGameList() {
|
||||
fun reloadGameList(ignoreCache: Boolean = false) {
|
||||
var storage = activity?.storageHelper ?: return
|
||||
|
||||
if(isLoading)
|
||||
@ -77,27 +77,32 @@ class HomeViewModel(
|
||||
|
||||
isLoading = true
|
||||
|
||||
val files = mutableListOf<GameModel>()
|
||||
if(!ignoreCache) {
|
||||
val files = mutableListOf<GameModel>()
|
||||
|
||||
thread {
|
||||
try {
|
||||
for (file in folder.search(false, DocumentFileType.FILE)) {
|
||||
if (file.extension == "xci" || file.extension == "nsp")
|
||||
activity.let {
|
||||
files.add(GameModel(file, it))
|
||||
}
|
||||
thread {
|
||||
try {
|
||||
for (file in folder.search(false, DocumentFileType.FILE)) {
|
||||
if (file.extension == "xci" || file.extension == "nsp")
|
||||
activity.let {
|
||||
files.add(GameModel(file, it))
|
||||
}
|
||||
}
|
||||
|
||||
loadedCache = files.toList()
|
||||
|
||||
isLoading = false
|
||||
|
||||
applyFilter()
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
loadedCache = files.toList()
|
||||
|
||||
isLoading = false
|
||||
|
||||
applyFilter()
|
||||
}
|
||||
finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
else{
|
||||
isLoading = false
|
||||
applyFilter()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyFilter() {
|
||||
@ -109,6 +114,10 @@ class HomeViewModel(
|
||||
|
||||
fun setViewList(list: SnapshotStateList<GameModel>) {
|
||||
gameList = list
|
||||
reloadGameList()
|
||||
reloadGameList(loadedCache.isNotEmpty())
|
||||
}
|
||||
|
||||
fun clearLoadedCache(){
|
||||
loadedCache = listOf()
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +169,47 @@ class MainViewModel(val activity: MainActivity) {
|
||||
return true
|
||||
}
|
||||
|
||||
fun clearPptcCache(titleId :String){
|
||||
if(titleId.isNotEmpty()){
|
||||
val basePath = MainActivity.AppPath + "/games/$titleId/cache/cpu"
|
||||
if(File(basePath).exists()){
|
||||
var caches = mutableListOf<String>()
|
||||
|
||||
val mainCache = basePath + "${File.separator}0"
|
||||
File(mainCache).listFiles()?.forEach {
|
||||
if(it.isFile && it.name.endsWith(".cache"))
|
||||
caches.add(it.absolutePath)
|
||||
}
|
||||
val backupCache = basePath + "${File.separator}1"
|
||||
File(backupCache).listFiles()?.forEach {
|
||||
if(it.isFile && it.name.endsWith(".cache"))
|
||||
caches.add(it.absolutePath)
|
||||
}
|
||||
for(path in caches)
|
||||
File(path).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun purgeShaderCache(titleId :String) {
|
||||
if(titleId.isNotEmpty()){
|
||||
val basePath = MainActivity.AppPath + "/games/$titleId/cache/shader"
|
||||
if(File(basePath).exists()){
|
||||
var caches = mutableListOf<String>()
|
||||
File(basePath).listFiles()?.forEach {
|
||||
if(!it.isFile)
|
||||
it.delete()
|
||||
else{
|
||||
if(it.name.endsWith(".toc") || it.name.endsWith(".data"))
|
||||
caches.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
for(path in caches)
|
||||
File(path).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setStatStates(
|
||||
fifo: MutableState<Double>,
|
||||
gameFps: MutableState<Double>,
|
||||
|
@ -4,27 +4,17 @@ import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.anggrayudi.storage.callback.FileCallback
|
||||
import com.anggrayudi.storage.file.DocumentFileCompat
|
||||
import com.anggrayudi.storage.file.DocumentFileType
|
||||
import com.anggrayudi.storage.file.copyFileTo
|
||||
import com.anggrayudi.storage.file.getAbsolutePath
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.ryujinx.android.Helpers
|
||||
import org.ryujinx.android.MainActivity
|
||||
import java.io.File
|
||||
import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import kotlin.math.max
|
||||
|
||||
class TitleUpdateViewModel(val titleId: String) {
|
||||
private var canClose: MutableState<Boolean>? = null
|
||||
private var basePath: String
|
||||
private var updateJsonName = "updates.json"
|
||||
private var stagingUpdateJsonName = "staging_updates.json"
|
||||
private var storageHelper: SimpleStorageHelper
|
||||
var pathsState: SnapshotStateList<String>? = null
|
||||
|
||||
@ -37,32 +27,37 @@ class TitleUpdateViewModel(val titleId: String) {
|
||||
return
|
||||
|
||||
data?.paths?.apply {
|
||||
removeAt(index - 1)
|
||||
val removed = removeAt(index - 1)
|
||||
File(removed).deleteRecursively()
|
||||
pathsState?.clear()
|
||||
pathsState?.addAll(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Add() {
|
||||
fun Add(
|
||||
isCopying: MutableState<Boolean>,
|
||||
copyProgress: MutableState<Float>,
|
||||
currentProgressName: MutableState<String>
|
||||
) {
|
||||
val callBack = storageHelper.onFileSelected
|
||||
|
||||
storageHelper.onFileSelected = { requestCode, files ->
|
||||
run {
|
||||
storageHelper.onFileSelected = callBack
|
||||
if(requestCode == UpdateRequestCode)
|
||||
{
|
||||
if (requestCode == UpdateRequestCode) {
|
||||
val file = files.firstOrNull()
|
||||
file?.apply {
|
||||
val path = file.getAbsolutePath(storageHelper.storage.context)
|
||||
if(path.isNotEmpty()){
|
||||
data?.apply {
|
||||
if(!paths.contains(path)) {
|
||||
paths.add(path)
|
||||
pathsState?.clear()
|
||||
pathsState?.addAll(paths)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy updates to internal data folder
|
||||
val updatePath = "$basePath/update"
|
||||
File(updatePath).mkdirs()
|
||||
Helpers.copyToData(
|
||||
this,
|
||||
updatePath,
|
||||
storageHelper,
|
||||
isCopying,
|
||||
copyProgress,
|
||||
currentProgressName, ::refreshPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,128 +65,60 @@ class TitleUpdateViewModel(val titleId: String) {
|
||||
storageHelper.openFilePicker(UpdateRequestCode)
|
||||
}
|
||||
|
||||
fun refreshPaths() {
|
||||
data?.apply {
|
||||
val updatePath = "$basePath/update"
|
||||
val existingPaths = mutableListOf<String>()
|
||||
File(updatePath).listFiles()?.forEach { existingPaths.add(it.absolutePath) }
|
||||
|
||||
if (!existingPaths.contains(selected)) {
|
||||
selected = ""
|
||||
}
|
||||
pathsState?.clear()
|
||||
pathsState?.addAll(existingPaths)
|
||||
paths = existingPaths
|
||||
canClose?.apply {
|
||||
value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun save(
|
||||
index: Int,
|
||||
isCopying: MutableState<Boolean>,
|
||||
openDialog: MutableState<Boolean>,
|
||||
copyProgress: MutableState<Float>,
|
||||
currentProgressName: MutableState<String>
|
||||
openDialog: MutableState<Boolean>
|
||||
) {
|
||||
data?.apply {
|
||||
val updatePath = "$basePath/update"
|
||||
this.selected = ""
|
||||
if (paths.isNotEmpty() && index > 0) {
|
||||
val ind = max(index - 1, paths.count() - 1)
|
||||
this.selected = paths[ind]
|
||||
}
|
||||
val gson = Gson()
|
||||
var json = gson.toJson(this)
|
||||
File(basePath).mkdirs()
|
||||
File("$basePath/$stagingUpdateJsonName").writeText(json)
|
||||
|
||||
// Copy updates to internal data folder
|
||||
val updatePath = "$basePath/update"
|
||||
File(updatePath).mkdirs()
|
||||
|
||||
val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
var metadata = TitleUpdateMetadata()
|
||||
var queue: Queue<String> = LinkedList()
|
||||
val savedUpdates = mutableListOf<String>()
|
||||
File(updatePath).listFiles()?.forEach { savedUpdates.add(it.absolutePath) }
|
||||
metadata.paths = savedUpdates
|
||||
|
||||
var callback: FileCallback? = null
|
||||
|
||||
fun copy(path: String) {
|
||||
isCopying.value = true
|
||||
val documentFile = DocumentFileCompat.fromFullPath(
|
||||
storageHelper.storage.context,
|
||||
path,
|
||||
DocumentFileType.FILE
|
||||
)
|
||||
documentFile?.apply {
|
||||
val stagedPath = "$basePath/${name}"
|
||||
if (!File(stagedPath).exists()) {
|
||||
var file = this
|
||||
ioScope.launch {
|
||||
file.copyFileTo(
|
||||
storageHelper.storage.context,
|
||||
File(updatePath),
|
||||
callback = callback!!
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
metadata.paths.add(stagedPath)
|
||||
}
|
||||
}
|
||||
val selectedName = File(selected).name
|
||||
val newSelectedPath = "$updatePath/$selectedName"
|
||||
if (File(newSelectedPath).exists()) {
|
||||
metadata.selected = newSelectedPath
|
||||
}
|
||||
|
||||
fun finish() {
|
||||
val savedUpdates = mutableListOf<String>()
|
||||
File(updatePath).listFiles()?.forEach { savedUpdates.add(it.absolutePath) }
|
||||
var missingFiles =
|
||||
savedUpdates.filter { i -> paths.find { it.endsWith(File(i).name) } == null }
|
||||
for (path in missingFiles) {
|
||||
File(path).delete()
|
||||
}
|
||||
var json = gson.toJson(metadata)
|
||||
File("$basePath/$updateJsonName").writeText(json)
|
||||
|
||||
val selectedName = File(selected).name
|
||||
val newSelectedPath = "$updatePath/$selectedName"
|
||||
if (File(newSelectedPath).exists()) {
|
||||
metadata.selected = newSelectedPath
|
||||
}
|
||||
|
||||
json = gson.toJson(metadata)
|
||||
File("$basePath/$updateJsonName").writeText(json)
|
||||
|
||||
openDialog.value = false
|
||||
isCopying.value = false
|
||||
}
|
||||
callback = object : FileCallback() {
|
||||
override fun onFailed(errorCode: FileCallback.ErrorCode) {
|
||||
super.onFailed(errorCode)
|
||||
}
|
||||
|
||||
override fun onStart(file: Any, workerThread: Thread): Long {
|
||||
copyProgress.value = 0f
|
||||
|
||||
(file as DocumentFile)?.apply {
|
||||
currentProgressName.value = "Copying ${file.name}"
|
||||
}
|
||||
return super.onStart(file, workerThread)
|
||||
}
|
||||
|
||||
override fun onReport(report: Report) {
|
||||
super.onReport(report)
|
||||
|
||||
copyProgress.value = report.progress / 100f
|
||||
}
|
||||
|
||||
override fun onCompleted(result: Any) {
|
||||
super.onCompleted(result)
|
||||
|
||||
if (queue.isNotEmpty())
|
||||
copy(queue.remove())
|
||||
else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
for (path in paths) {
|
||||
queue.add(path)
|
||||
}
|
||||
|
||||
ioScope.launch {
|
||||
if (queue.isNotEmpty()) {
|
||||
copy(queue.remove())
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
||||
openDialog.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun setPaths(paths: SnapshotStateList<String>) {
|
||||
fun setPaths(paths: SnapshotStateList<String>, canClose: MutableState<Boolean>) {
|
||||
pathsState = paths
|
||||
this.canClose = canClose
|
||||
data?.apply {
|
||||
pathsState?.clear()
|
||||
pathsState?.addAll(this.paths)
|
||||
@ -203,29 +130,14 @@ class TitleUpdateViewModel(val titleId: String) {
|
||||
|
||||
init {
|
||||
basePath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
|
||||
val stagingJson = "${basePath}/${stagingUpdateJsonName}"
|
||||
jsonPath = "${basePath}/${updateJsonName}"
|
||||
|
||||
data = TitleUpdateMetadata()
|
||||
if (File(stagingJson).exists()) {
|
||||
if (File(jsonPath).exists()) {
|
||||
val gson = Gson()
|
||||
data = gson.fromJson(File(stagingJson).readText(), TitleUpdateMetadata::class.java)
|
||||
data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
|
||||
|
||||
data?.apply {
|
||||
val existingPaths = mutableListOf<String>()
|
||||
for (path in paths) {
|
||||
if (File(path).exists()) {
|
||||
existingPaths.add(path)
|
||||
}
|
||||
}
|
||||
|
||||
if(!existingPaths.contains(selected)){
|
||||
selected = ""
|
||||
}
|
||||
pathsState?.clear()
|
||||
pathsState?.addAll(existingPaths)
|
||||
paths = existingPaths
|
||||
}
|
||||
refreshPaths()
|
||||
}
|
||||
|
||||
storageHelper = MainActivity.StorageHelper!!
|
||||
|
@ -61,6 +61,7 @@ import androidx.navigation.NavHostController
|
||||
import coil.compose.AsyncImage
|
||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||
import org.ryujinx.android.MainActivity
|
||||
import org.ryujinx.android.NativeHelpers
|
||||
import org.ryujinx.android.RyujinxNative
|
||||
import org.ryujinx.android.viewmodels.GameModel
|
||||
import org.ryujinx.android.viewmodels.HomeViewModel
|
||||
@ -84,6 +85,7 @@ class HomeViews {
|
||||
val showAppActions = remember { mutableStateOf(false) }
|
||||
val showLoading = remember { mutableStateOf(false) }
|
||||
val openTitleUpdateDialog = remember { mutableStateOf(false) }
|
||||
val canClose = remember { mutableStateOf(true) }
|
||||
val openDlcDialog = remember { mutableStateOf(false) }
|
||||
val query = remember {
|
||||
mutableStateOf("")
|
||||
@ -102,10 +104,11 @@ class HomeViews {
|
||||
val pic = remember {
|
||||
mutableStateOf(ByteArray(0))
|
||||
}
|
||||
|
||||
if(refreshUser.value){
|
||||
user.value = native.userGetOpenedUser()
|
||||
if(user.value.isNotEmpty()) {
|
||||
|
||||
if (refreshUser.value) {
|
||||
native.userGetOpenedUser()
|
||||
user.value = NativeHelpers().popStringJava()
|
||||
if (user.value.isNotEmpty()) {
|
||||
val decoder = Base64.getDecoder()
|
||||
pic.value = decoder.decode(native.userGetUserPicture(user.value))
|
||||
}
|
||||
@ -145,7 +148,7 @@ class HomeViews {
|
||||
IconButton(onClick = {
|
||||
navController?.navigate("user")
|
||||
}) {
|
||||
if(pic.value.isNotEmpty()) {
|
||||
if (pic.value.isNotEmpty()) {
|
||||
Image(
|
||||
bitmap = BitmapFactory.decodeByteArray(
|
||||
pic.value,
|
||||
@ -160,8 +163,7 @@ class HomeViews {
|
||||
.size(52.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
Icon(
|
||||
Icons.Filled.Person,
|
||||
contentDescription = "user"
|
||||
@ -204,29 +206,29 @@ class HomeViews {
|
||||
DropdownMenu(
|
||||
expanded = showAppMenu.value,
|
||||
onDismissRequest = { showAppMenu.value = false }) {
|
||||
DropdownMenuItem(text = {
|
||||
Text(text = "Clear PPTC Cache")
|
||||
}, onClick = {
|
||||
showAppMenu.value = false
|
||||
viewModel.mainViewModel?.clearPptcCache(viewModel.mainViewModel?.selected?.titleId ?: "")
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(text = "Purge Shader Cache")
|
||||
}, onClick = {
|
||||
showAppMenu.value = false
|
||||
viewModel.mainViewModel?.purgeShaderCache(viewModel.mainViewModel?.selected?.titleId ?: "")
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(text = "Manage Updates")
|
||||
}, onClick = {
|
||||
showAppMenu.value = false
|
||||
openTitleUpdateDialog.value = true
|
||||
}, leadingIcon = {
|
||||
Icon(
|
||||
imageVector = org.ryujinx.android.Icons.gameUpdate(),
|
||||
contentDescription = "Updates",
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
})
|
||||
DropdownMenuItem(text = {
|
||||
Text(text = "Manage DLC")
|
||||
}, onClick = {
|
||||
showAppMenu.value = false
|
||||
openDlcDialog.value = true
|
||||
}, leadingIcon = {
|
||||
Icon(
|
||||
imageVector = org.ryujinx.android.Icons.download(),
|
||||
contentDescription = "Dlc",
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -255,11 +257,10 @@ class HomeViews {
|
||||
val list = remember {
|
||||
mutableStateListOf<GameModel>()
|
||||
}
|
||||
|
||||
|
||||
if (refresh.value) {
|
||||
viewModel.setViewList(list)
|
||||
refresh.value = false
|
||||
showAppActions.value = false
|
||||
}
|
||||
val selectedModel = remember {
|
||||
mutableStateOf(viewModel.mainViewModel?.selected)
|
||||
@ -323,7 +324,7 @@ class HomeViews {
|
||||
) {
|
||||
val titleId = viewModel.mainViewModel?.selected?.titleId ?: ""
|
||||
val name = viewModel.mainViewModel?.selected?.titleName ?: ""
|
||||
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog)
|
||||
TitleUpdateViews.Main(titleId, name, openTitleUpdateDialog, canClose)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -325,6 +325,8 @@ class SettingViews {
|
||||
AlertDialog(onDismissRequest = {
|
||||
showImportCompletion.value = false
|
||||
importFile.value = null
|
||||
mainViewModel.requestUserRefresh()
|
||||
mainViewModel.homeViewModel.clearLoadedCache()
|
||||
}) {
|
||||
Card(
|
||||
modifier = Modifier,
|
||||
|
@ -27,11 +27,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.ryujinx.android.viewmodels.TitleUpdateViewModel
|
||||
import java.io.File
|
||||
|
||||
class TitleUpdateViews {
|
||||
companion object {
|
||||
@Composable
|
||||
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>) {
|
||||
fun Main(titleId: String, name: String, openDialog: MutableState<Boolean>, canClose: MutableState<Boolean>) {
|
||||
val viewModel = TitleUpdateViewModel(titleId)
|
||||
|
||||
val selected = remember { mutableStateOf(0) }
|
||||
@ -46,6 +47,9 @@ class TitleUpdateViews {
|
||||
val copyProgress = remember {
|
||||
mutableStateOf(0.0f)
|
||||
}
|
||||
var currentProgressName = remember {
|
||||
mutableStateOf("Starting Copy")
|
||||
}
|
||||
Column {
|
||||
Text(text = "Updates for ${name}", textAlign = TextAlign.Center)
|
||||
Surface(
|
||||
@ -77,7 +81,7 @@ class TitleUpdateViews {
|
||||
mutableStateListOf<String>()
|
||||
}
|
||||
|
||||
viewModel.setPaths(paths)
|
||||
viewModel.setPaths(paths, canClose)
|
||||
var index = 1
|
||||
for (path in paths) {
|
||||
val i = index
|
||||
@ -86,7 +90,7 @@ class TitleUpdateViews {
|
||||
selected = (selected.value == i),
|
||||
onClick = { selected.value = i })
|
||||
Text(
|
||||
text = path,
|
||||
text = File(path).name,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.CenterVertically)
|
||||
@ -111,7 +115,7 @@ class TitleUpdateViews {
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.Add()
|
||||
viewModel.Add(isCopying, copyProgress, currentProgressName)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
@ -122,22 +126,33 @@ class TitleUpdateViews {
|
||||
}
|
||||
|
||||
}
|
||||
var currentProgressName = remember {
|
||||
mutableStateOf("Starting Copy")
|
||||
}
|
||||
if (isCopying.value) {
|
||||
Text(text = "Copying updates to local storage")
|
||||
Text(text = currentProgressName.value)
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = copyProgress.value
|
||||
)
|
||||
Row {
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = copyProgress.value
|
||||
)
|
||||
TextButton(
|
||||
onClick = {
|
||||
isCopying.value = false
|
||||
canClose.value = true
|
||||
viewModel.refreshPaths()
|
||||
},
|
||||
) {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
TextButton(
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
onClick = {
|
||||
viewModel.save(selected.value, isCopying, openDialog, copyProgress, currentProgressName)
|
||||
if (!isCopying.value) {
|
||||
canClose.value = true
|
||||
viewModel.save(selected.value, openDialog)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text("Save")
|
||||
|
@ -36,6 +36,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import org.ryujinx.android.NativeHelpers
|
||||
import org.ryujinx.android.RyujinxNative
|
||||
import org.ryujinx.android.viewmodels.MainViewModel
|
||||
import java.util.Base64
|
||||
@ -47,8 +48,9 @@ class UserViews {
|
||||
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) {
|
||||
val ryujinxNative = RyujinxNative()
|
||||
val decoder = Base64.getDecoder()
|
||||
ryujinxNative.userGetOpenedUser()
|
||||
val openedUser = remember {
|
||||
mutableStateOf(ryujinxNative.userGetOpenedUser())
|
||||
mutableStateOf(NativeHelpers().popStringJava())
|
||||
}
|
||||
|
||||
val openedUserPic = remember {
|
||||
|
Loading…
x
Reference in New Issue
Block a user