android - improve game update selection

This commit is contained in:
Emmanuel Hansen 2023-10-28 14:45:23 +00:00
parent 2240d87902
commit 14d6e280f7
15 changed files with 274 additions and 201 deletions

View File

@ -41,6 +41,9 @@ namespace LibRyujinx
[DllImport("libryujinxjni")] [DllImport("libryujinxjni")]
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch); private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
[DllImport("libryujinxjni")]
private extern static void pushString(string ch);
[DllImport("libryujinxjni")] [DllImport("libryujinxjni")]
internal extern static void setRenderingThread(); internal extern static void setRenderingThread();
@ -511,11 +514,11 @@ namespace LibRyujinx
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetOpenedUser")] [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(); var userId = GetOpenedUser();
return CreateString(jEnv, userId); pushString(userId);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")] [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userGetUserPicture")]

View File

@ -728,6 +728,7 @@ namespace LibRyujinx
{ {
VirtualFileSystem.ReloadKeySet(); VirtualFileSystem.ReloadKeySet();
ContentManager = new ContentManager(VirtualFileSystem); ContentManager = new ContentManager(VirtualFileSystem);
AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient);
} }
internal void DisposeContext() internal void DisposeContext()

View File

@ -45,5 +45,6 @@ long _currentRenderingThreadId = 0;
JavaVM* _vm = nullptr; JavaVM* _vm = nullptr;
jobject _mainActivity = nullptr; jobject _mainActivity = nullptr;
jclass _mainActivityClass = nullptr; jclass _mainActivityClass = nullptr;
std::string _currentString = "";
#endif //RYUJINXNATIVE_RYUIJNX_H #endif //RYUJINXNATIVE_RYUIJNX_H

View File

@ -311,3 +311,25 @@ JNIEXPORT jstring JNICALL
Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz) { Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz) {
return createStringFromStdString(env, progressInfo); 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();
}

View File

@ -354,6 +354,7 @@ class GameActivity : BaseActivity() {
.padding(16.dp) .padding(16.dp)
) { ) {
Button(onClick = { Button(onClick = {
showBackNotice.value = false
mainViewModel.closeGame() mainViewModel.closeGame()
setFullScreen(false) setFullScreen(false)
finishActivity(0) finishActivity(0)

View File

@ -9,7 +9,13 @@ import android.provider.DocumentsContract
import android.provider.MediaStore import android.provider.MediaStore
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.documentfile.provider.DocumentFile 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 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 net.lingala.zip4j.io.inputstream.ZipInputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
@ -66,6 +72,61 @@ class Helpers {
} }
return null 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( private fun getDataColumn(
context: Context, context: Context,

View File

@ -28,4 +28,6 @@ class NativeHelpers {
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
external fun getProgressInfo() : String external fun getProgressInfo() : String
external fun getProgressValue() : Float external fun getProgressValue() : Float
external fun pushStringJava(string: String)
external fun popStringJava() : String
} }

View File

@ -53,7 +53,7 @@ class RyujinxNative {
external fun deviceSignalEmulationClose() external fun deviceSignalEmulationClose()
external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String external fun deviceGetDlcTitleId(path: String, ncaPath: String) : String
external fun deviceGetDlcContentList(path: String, titleId: Long) : Array<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 userGetUserPicture(userId: String) : String
external fun userSetUserPicture(userId: String, picture: String) external fun userSetUserPicture(userId: String, picture: String)
external fun userGetUserName(userId: String) : String external fun userGetUserName(userId: String) : String

View File

@ -68,7 +68,7 @@ class HomeViewModel(
) )
} }
fun reloadGameList() { fun reloadGameList(ignoreCache: Boolean = false) {
var storage = activity?.storageHelper ?: return var storage = activity?.storageHelper ?: return
if(isLoading) if(isLoading)
@ -77,27 +77,32 @@ class HomeViewModel(
isLoading = true isLoading = true
val files = mutableListOf<GameModel>() if(!ignoreCache) {
val files = mutableListOf<GameModel>()
thread { thread {
try { try {
for (file in folder.search(false, DocumentFileType.FILE)) { for (file in folder.search(false, DocumentFileType.FILE)) {
if (file.extension == "xci" || file.extension == "nsp") if (file.extension == "xci" || file.extension == "nsp")
activity.let { activity.let {
files.add(GameModel(file, it)) 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() { private fun applyFilter() {
@ -109,6 +114,10 @@ class HomeViewModel(
fun setViewList(list: SnapshotStateList<GameModel>) { fun setViewList(list: SnapshotStateList<GameModel>) {
gameList = list gameList = list
reloadGameList() reloadGameList(loadedCache.isNotEmpty())
}
fun clearLoadedCache(){
loadedCache = listOf()
} }
} }

View File

@ -169,6 +169,47 @@ class MainViewModel(val activity: MainActivity) {
return true 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( fun setStatStates(
fifo: MutableState<Double>, fifo: MutableState<Double>,
gameFps: MutableState<Double>, gameFps: MutableState<Double>,

View File

@ -4,27 +4,17 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.text.toLowerCase
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.SimpleStorageHelper 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 com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope import org.ryujinx.android.Helpers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import java.io.File import java.io.File
import java.util.LinkedList
import java.util.Queue
import kotlin.math.max import kotlin.math.max
class TitleUpdateViewModel(val titleId: String) { class TitleUpdateViewModel(val titleId: String) {
private var canClose: MutableState<Boolean>? = null
private var basePath: String private var basePath: String
private var updateJsonName = "updates.json" private var updateJsonName = "updates.json"
private var stagingUpdateJsonName = "staging_updates.json"
private var storageHelper: SimpleStorageHelper private var storageHelper: SimpleStorageHelper
var pathsState: SnapshotStateList<String>? = null var pathsState: SnapshotStateList<String>? = null
@ -37,32 +27,37 @@ class TitleUpdateViewModel(val titleId: String) {
return return
data?.paths?.apply { data?.paths?.apply {
removeAt(index - 1) val removed = removeAt(index - 1)
File(removed).deleteRecursively()
pathsState?.clear() pathsState?.clear()
pathsState?.addAll(this) pathsState?.addAll(this)
} }
} }
fun Add() { fun Add(
isCopying: MutableState<Boolean>,
copyProgress: MutableState<Float>,
currentProgressName: MutableState<String>
) {
val callBack = storageHelper.onFileSelected val callBack = storageHelper.onFileSelected
storageHelper.onFileSelected = { requestCode, files -> storageHelper.onFileSelected = { requestCode, files ->
run { run {
storageHelper.onFileSelected = callBack storageHelper.onFileSelected = callBack
if(requestCode == UpdateRequestCode) if (requestCode == UpdateRequestCode) {
{
val file = files.firstOrNull() val file = files.firstOrNull()
file?.apply { file?.apply {
val path = file.getAbsolutePath(storageHelper.storage.context) // Copy updates to internal data folder
if(path.isNotEmpty()){ val updatePath = "$basePath/update"
data?.apply { File(updatePath).mkdirs()
if(!paths.contains(path)) { Helpers.copyToData(
paths.add(path) this,
pathsState?.clear() updatePath,
pathsState?.addAll(paths) storageHelper,
} isCopying,
} copyProgress,
} currentProgressName, ::refreshPaths
)
} }
} }
} }
@ -70,128 +65,60 @@ class TitleUpdateViewModel(val titleId: String) {
storageHelper.openFilePicker(UpdateRequestCode) 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( fun save(
index: Int, index: Int,
isCopying: MutableState<Boolean>, openDialog: MutableState<Boolean>
openDialog: MutableState<Boolean>,
copyProgress: MutableState<Float>,
currentProgressName: MutableState<String>
) { ) {
data?.apply { data?.apply {
val updatePath = "$basePath/update"
this.selected = "" this.selected = ""
if (paths.isNotEmpty() && index > 0) { if (paths.isNotEmpty() && index > 0) {
val ind = max(index - 1, paths.count() - 1) val ind = max(index - 1, paths.count() - 1)
this.selected = paths[ind] this.selected = paths[ind]
} }
val gson = Gson() val gson = Gson()
var json = gson.toJson(this)
File(basePath).mkdirs() 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 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 val selectedName = File(selected).name
val newSelectedPath = "$updatePath/$selectedName"
fun copy(path: String) { if (File(newSelectedPath).exists()) {
isCopying.value = true metadata.selected = newSelectedPath
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)
}
}
} }
fun finish() { var json = gson.toJson(metadata)
val savedUpdates = mutableListOf<String>() File("$basePath/$updateJsonName").writeText(json)
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()
}
val selectedName = File(selected).name openDialog.value = false
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()
}
}
} }
} }
fun setPaths(paths: SnapshotStateList<String>) { fun setPaths(paths: SnapshotStateList<String>, canClose: MutableState<Boolean>) {
pathsState = paths pathsState = paths
this.canClose = canClose
data?.apply { data?.apply {
pathsState?.clear() pathsState?.clear()
pathsState?.addAll(this.paths) pathsState?.addAll(this.paths)
@ -203,29 +130,14 @@ class TitleUpdateViewModel(val titleId: String) {
init { init {
basePath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current) basePath = MainActivity.AppPath + "/games/" + titleId.toLowerCase(Locale.current)
val stagingJson = "${basePath}/${stagingUpdateJsonName}"
jsonPath = "${basePath}/${updateJsonName}" jsonPath = "${basePath}/${updateJsonName}"
data = TitleUpdateMetadata() data = TitleUpdateMetadata()
if (File(stagingJson).exists()) { if (File(jsonPath).exists()) {
val gson = Gson() val gson = Gson()
data = gson.fromJson(File(stagingJson).readText(), TitleUpdateMetadata::class.java) data = gson.fromJson(File(jsonPath).readText(), TitleUpdateMetadata::class.java)
data?.apply { refreshPaths()
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
}
} }
storageHelper = MainActivity.StorageHelper!! storageHelper = MainActivity.StorageHelper!!

View File

@ -61,6 +61,7 @@ import androidx.navigation.NavHostController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.anggrayudi.storage.extension.launchOnUiThread import com.anggrayudi.storage.extension.launchOnUiThread
import org.ryujinx.android.MainActivity import org.ryujinx.android.MainActivity
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.viewmodels.GameModel import org.ryujinx.android.viewmodels.GameModel
import org.ryujinx.android.viewmodels.HomeViewModel import org.ryujinx.android.viewmodels.HomeViewModel
@ -84,6 +85,7 @@ class HomeViews {
val showAppActions = remember { mutableStateOf(false) } val showAppActions = remember { mutableStateOf(false) }
val showLoading = remember { mutableStateOf(false) } val showLoading = remember { mutableStateOf(false) }
val openTitleUpdateDialog = remember { mutableStateOf(false) } val openTitleUpdateDialog = remember { mutableStateOf(false) }
val canClose = remember { mutableStateOf(true) }
val openDlcDialog = remember { mutableStateOf(false) } val openDlcDialog = remember { mutableStateOf(false) }
val query = remember { val query = remember {
mutableStateOf("") mutableStateOf("")
@ -102,10 +104,11 @@ class HomeViews {
val pic = remember { val pic = remember {
mutableStateOf(ByteArray(0)) mutableStateOf(ByteArray(0))
} }
if(refreshUser.value){ if (refreshUser.value) {
user.value = native.userGetOpenedUser() native.userGetOpenedUser()
if(user.value.isNotEmpty()) { user.value = NativeHelpers().popStringJava()
if (user.value.isNotEmpty()) {
val decoder = Base64.getDecoder() val decoder = Base64.getDecoder()
pic.value = decoder.decode(native.userGetUserPicture(user.value)) pic.value = decoder.decode(native.userGetUserPicture(user.value))
} }
@ -145,7 +148,7 @@ class HomeViews {
IconButton(onClick = { IconButton(onClick = {
navController?.navigate("user") navController?.navigate("user")
}) { }) {
if(pic.value.isNotEmpty()) { if (pic.value.isNotEmpty()) {
Image( Image(
bitmap = BitmapFactory.decodeByteArray( bitmap = BitmapFactory.decodeByteArray(
pic.value, pic.value,
@ -160,8 +163,7 @@ class HomeViews {
.size(52.dp) .size(52.dp)
.clip(CircleShape) .clip(CircleShape)
) )
} } else {
else{
Icon( Icon(
Icons.Filled.Person, Icons.Filled.Person,
contentDescription = "user" contentDescription = "user"
@ -204,29 +206,29 @@ class HomeViews {
DropdownMenu( DropdownMenu(
expanded = showAppMenu.value, expanded = showAppMenu.value,
onDismissRequest = { showAppMenu.value = false }) { 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 = { DropdownMenuItem(text = {
Text(text = "Manage Updates") Text(text = "Manage Updates")
}, onClick = { }, onClick = {
showAppMenu.value = false showAppMenu.value = false
openTitleUpdateDialog.value = true openTitleUpdateDialog.value = true
}, leadingIcon = {
Icon(
imageVector = org.ryujinx.android.Icons.gameUpdate(),
contentDescription = "Updates",
tint = MaterialTheme.colorScheme.onSurface
)
}) })
DropdownMenuItem(text = { DropdownMenuItem(text = {
Text(text = "Manage DLC") Text(text = "Manage DLC")
}, onClick = { }, onClick = {
showAppMenu.value = false showAppMenu.value = false
openDlcDialog.value = true 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 { val list = remember {
mutableStateListOf<GameModel>() mutableStateListOf<GameModel>()
} }
if (refresh.value) { if (refresh.value) {
viewModel.setViewList(list) viewModel.setViewList(list)
refresh.value = false refresh.value = false
showAppActions.value = false
} }
val selectedModel = remember { val selectedModel = remember {
mutableStateOf(viewModel.mainViewModel?.selected) mutableStateOf(viewModel.mainViewModel?.selected)
@ -323,7 +324,7 @@ class HomeViews {
) { ) {
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, canClose)
} }
} }

View File

@ -325,6 +325,8 @@ class SettingViews {
AlertDialog(onDismissRequest = { AlertDialog(onDismissRequest = {
showImportCompletion.value = false showImportCompletion.value = false
importFile.value = null importFile.value = null
mainViewModel.requestUserRefresh()
mainViewModel.homeViewModel.clearLoadedCache()
}) { }) {
Card( Card(
modifier = Modifier, modifier = Modifier,

View File

@ -27,11 +27,12 @@ import androidx.compose.ui.Modifier
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 org.ryujinx.android.viewmodels.TitleUpdateViewModel import org.ryujinx.android.viewmodels.TitleUpdateViewModel
import java.io.File
class TitleUpdateViews { class TitleUpdateViews {
companion object { companion object {
@Composable @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 viewModel = TitleUpdateViewModel(titleId)
val selected = remember { mutableStateOf(0) } val selected = remember { mutableStateOf(0) }
@ -46,6 +47,9 @@ class TitleUpdateViews {
val copyProgress = remember { val copyProgress = remember {
mutableStateOf(0.0f) mutableStateOf(0.0f)
} }
var currentProgressName = remember {
mutableStateOf("Starting Copy")
}
Column { Column {
Text(text = "Updates for ${name}", textAlign = TextAlign.Center) Text(text = "Updates for ${name}", textAlign = TextAlign.Center)
Surface( Surface(
@ -77,7 +81,7 @@ class TitleUpdateViews {
mutableStateListOf<String>() mutableStateListOf<String>()
} }
viewModel.setPaths(paths) viewModel.setPaths(paths, canClose)
var index = 1 var index = 1
for (path in paths) { for (path in paths) {
val i = index val i = index
@ -86,7 +90,7 @@ class TitleUpdateViews {
selected = (selected.value == i), selected = (selected.value == i),
onClick = { selected.value = i }) onClick = { selected.value = i })
Text( Text(
text = path, text = File(path).name,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
@ -111,7 +115,7 @@ class TitleUpdateViews {
IconButton( IconButton(
onClick = { onClick = {
viewModel.Add() viewModel.Add(isCopying, copyProgress, currentProgressName)
} }
) { ) {
Icon( Icon(
@ -122,22 +126,33 @@ class TitleUpdateViews {
} }
} }
var currentProgressName = remember {
mutableStateOf("Starting Copy")
}
if (isCopying.value) { if (isCopying.value) {
Text(text = "Copying updates to local storage") Text(text = "Copying updates to local storage")
Text(text = currentProgressName.value) Text(text = currentProgressName.value)
LinearProgressIndicator( Row {
modifier = Modifier.fillMaxWidth(), LinearProgressIndicator(
progress = copyProgress.value modifier = Modifier.fillMaxWidth(),
) progress = copyProgress.value
)
TextButton(
onClick = {
isCopying.value = false
canClose.value = true
viewModel.refreshPaths()
},
) {
Text("Cancel")
}
}
} }
Spacer(modifier = Modifier.height(18.dp)) Spacer(modifier = Modifier.height(18.dp))
TextButton( TextButton(
modifier = Modifier.align(Alignment.End), modifier = Modifier.align(Alignment.End),
onClick = { onClick = {
viewModel.save(selected.value, isCopying, openDialog, copyProgress, currentProgressName) if (!isCopying.value) {
canClose.value = true
viewModel.save(selected.value, openDialog)
}
}, },
) { ) {
Text("Save") Text("Save")

View File

@ -36,6 +36,7 @@ import androidx.compose.ui.layout.ContentScale
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.navigation.NavHostController import androidx.navigation.NavHostController
import org.ryujinx.android.NativeHelpers
import org.ryujinx.android.RyujinxNative import org.ryujinx.android.RyujinxNative
import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.MainViewModel
import java.util.Base64 import java.util.Base64
@ -47,8 +48,9 @@ class UserViews {
fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) { fun Main(viewModel: MainViewModel? = null, navController: NavHostController? = null) {
val ryujinxNative = RyujinxNative() val ryujinxNative = RyujinxNative()
val decoder = Base64.getDecoder() val decoder = Base64.getDecoder()
ryujinxNative.userGetOpenedUser()
val openedUser = remember { val openedUser = remember {
mutableStateOf(ryujinxNative.userGetOpenedUser()) mutableStateOf(NativeHelpers().popStringJava())
} }
val openedUserPic = remember { val openedUserPic = remember {