diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs index 76ee4039e..255adb456 100644 --- a/src/LibRyujinx/Android/JniExportedMethods.cs +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -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")] diff --git a/src/LibRyujinx/LibRyujinx.cs b/src/LibRyujinx/LibRyujinx.cs index 52318260f..51dd76156 100644 --- a/src/LibRyujinx/LibRyujinx.cs +++ b/src/LibRyujinx/LibRyujinx.cs @@ -728,6 +728,7 @@ namespace LibRyujinx { VirtualFileSystem.ReloadKeySet(); ContentManager = new ContentManager(VirtualFileSystem); + AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient); } internal void DisposeContext() diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h index b26687b44..87e7c3124 100644 --- a/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h +++ b/src/RyujinxAndroid/app/src/main/cpp/ryuijnx.h @@ -45,5 +45,6 @@ long _currentRenderingThreadId = 0; JavaVM* _vm = nullptr; jobject _mainActivity = nullptr; jclass _mainActivityClass = nullptr; +std::string _currentString = ""; #endif //RYUJINXNATIVE_RYUIJNX_H diff --git a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp index 278c176b5..095f462db 100644 --- a/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp +++ b/src/RyujinxAndroid/app/src/main/cpp/ryujinx.cpp @@ -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(); +} diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt index 8dc39bfeb..1670e879a 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameActivity.kt @@ -354,6 +354,7 @@ class GameActivity : BaseActivity() { .padding(16.dp) ) { Button(onClick = { + showBackNotice.value = false mainViewModel.closeGame() setFullScreen(false) finishActivity(0) diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt index 764d6edab..aef544c7a 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/Helpers.kt @@ -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, + copyProgress: MutableState, + currentProgressName: MutableState, + 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, diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt index eead88176..229b61a90 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/NativeHelpers.kt @@ -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 } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt index 88b266fed..4c990f094 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt @@ -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 - 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 diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt index dc34f6882..bbfeb9e8a 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/HomeViewModel.kt @@ -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() + if(!ignoreCache) { + val files = mutableListOf() - 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) { gameList = list - reloadGameList() + reloadGameList(loadedCache.isNotEmpty()) + } + + fun clearLoadedCache(){ + loadedCache = listOf() } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt index 734267720..0d3850425 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt @@ -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() + + 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() + 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, gameFps: MutableState, diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt index 8d3a53456..e42108220 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/TitleUpdateViewModel.kt @@ -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? = null private var basePath: String private var updateJsonName = "updates.json" - private var stagingUpdateJsonName = "staging_updates.json" private var storageHelper: SimpleStorageHelper var pathsState: SnapshotStateList? = 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, + copyProgress: MutableState, + currentProgressName: MutableState + ) { 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() + 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, - openDialog: MutableState, - copyProgress: MutableState, - currentProgressName: MutableState + openDialog: MutableState ) { 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 = LinkedList() + val savedUpdates = mutableListOf() + 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() - 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) { + fun setPaths(paths: SnapshotStateList, canClose: MutableState) { 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() - 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!! diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt index bd1911ad0..f567410f1 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/HomeViews.kt @@ -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() } - - 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) } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt index 5b28aad91..1870b4261 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/SettingViews.kt @@ -325,6 +325,8 @@ class SettingViews { AlertDialog(onDismissRequest = { showImportCompletion.value = false importFile.value = null + mainViewModel.requestUserRefresh() + mainViewModel.homeViewModel.clearLoadedCache() }) { Card( modifier = Modifier, diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt index 558bc559b..96347f259 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/TitleUpdateViews.kt @@ -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) { + fun Main(titleId: String, name: String, openDialog: MutableState, canClose: MutableState) { 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() } - 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") diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt index b41d17ea9..63f0f2379 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/views/UserViews.kt @@ -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 {