mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Update graphic packs
This commit is contained in:
+4
@@ -1,5 +1,6 @@
|
||||
package info.cemu.cemu.common.ui.components
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -53,6 +54,9 @@ fun FilledSearchToolbar(
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
var searchBarActive by remember { mutableStateOf(false) }
|
||||
|
||||
BackHandler(enabled = searchBarActive) { searchBarActive = false }
|
||||
|
||||
TopAppBar(
|
||||
actions = actions,
|
||||
title = {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
package info.cemu.cemu.gamelist
|
||||
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
@@ -92,6 +92,7 @@ fun GamesListScreen(
|
||||
gameListViewModel.setFilterText("")
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
topBar = {
|
||||
@@ -196,7 +197,7 @@ private fun GameList(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShaderCachesConfirmationDialog(
|
||||
private fun ShaderCachesConfirmationDialog(
|
||||
gameName: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
@@ -227,7 +228,7 @@ fun ShaderCachesConfirmationDialog(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GameListItem(
|
||||
private fun GameListItem(
|
||||
onStartGame: (Game) -> Unit,
|
||||
onIsFavoriteChanged: (Boolean) -> Unit,
|
||||
onEditGameProfile: () -> Unit,
|
||||
@@ -290,7 +291,7 @@ fun GameListItem(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GameContextMenu(
|
||||
private fun GameContextMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onIsFavoriteChanged: (Boolean) -> Unit,
|
||||
@@ -354,4 +355,3 @@ fun GameContextMenu(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
class GraphicPackDataNode(
|
||||
val id: Long,
|
||||
name: String,
|
||||
val path: String,
|
||||
enabled: Boolean,
|
||||
val parent: GraphicPackSectionNode?,
|
||||
) :
|
||||
GraphicPackNode(name) {
|
||||
constructor(
|
||||
id: Long,
|
||||
name: String,
|
||||
path: String,
|
||||
enabled: Boolean,
|
||||
titleIdInstalled: Boolean,
|
||||
parentNode: GraphicPackSectionNode,
|
||||
) : this(id, name, path, enabled, parentNode) {
|
||||
this.titleIdInstalled = titleIdInstalled
|
||||
}
|
||||
|
||||
var enabled: Boolean = enabled
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
parent?.updateEnabledCount(value)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.CreationExtras
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import info.cemu.cemu.nativeinterface.NativeGraphicPacks
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
data class Preset(
|
||||
val index: Int,
|
||||
val category: String?,
|
||||
val activePreset: String,
|
||||
val presets: List<String>,
|
||||
)
|
||||
|
||||
class GraphicPackDataViewModel(private val graphicPackNode: GraphicPackDataNode) : ViewModel() {
|
||||
private val nativeGraphicPack = NativeGraphicPacks.getGraphicPack(graphicPackNode.id)
|
||||
val description = nativeGraphicPack?.description ?: ""
|
||||
private var nativePresets: List<NativeGraphicPacks.GraphicPackPreset> = emptyList()
|
||||
private val _presets = MutableStateFlow<List<Preset>>(emptyList())
|
||||
val presets = _presets.asStateFlow()
|
||||
|
||||
val name = graphicPackNode.name
|
||||
|
||||
private var _enabled = MutableStateFlow(graphicPackNode.enabled)
|
||||
val enabled = _enabled.asStateFlow()
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
nativeGraphicPack?.setActive(enabled)
|
||||
_enabled.value = enabled
|
||||
graphicPackNode.enabled = enabled
|
||||
}
|
||||
|
||||
init {
|
||||
refreshPresets()
|
||||
}
|
||||
|
||||
private fun refreshPresets() {
|
||||
nativeGraphicPack?.reloadPresets()
|
||||
if (nativePresets == nativeGraphicPack?.presets) {
|
||||
return
|
||||
}
|
||||
nativePresets = nativeGraphicPack?.presets ?: emptyList()
|
||||
nativePresets.mapIndexed { index, preset ->
|
||||
Preset(
|
||||
index = index,
|
||||
category = preset.category,
|
||||
activePreset = preset.activePreset,
|
||||
presets = preset.presets,
|
||||
)
|
||||
}.let {
|
||||
_presets.value = it
|
||||
}
|
||||
}
|
||||
|
||||
fun setActivePreset(index: Int, activePreset: String) {
|
||||
_presets.value = _presets.value.toMutableList().apply {
|
||||
set(index, get(index).copy(activePreset = activePreset))
|
||||
}
|
||||
nativePresets[index].activePreset = activePreset
|
||||
refreshPresets()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val GRAPHIC_PACK_KEY = object : CreationExtras.Key<GraphicPackDataNode> {}
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
GraphicPackDataViewModel(
|
||||
this[GRAPHIC_PACK_KEY] as GraphicPackDataNode
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
abstract class GraphicPackNode(
|
||||
val name: String?
|
||||
) {
|
||||
var titleIdInstalled: Boolean = false
|
||||
protected set
|
||||
}
|
||||
+42
-8
@@ -3,21 +3,27 @@ package info.cemu.cemu.graphicpacks
|
||||
import info.cemu.cemu.nativeinterface.NativeGraphicPacks
|
||||
import kotlin.math.max
|
||||
|
||||
sealed class GraphicPackNode(
|
||||
val name: String?,
|
||||
val parent: GraphicPackSectionNode?
|
||||
) {
|
||||
var titleIdInstalled: Boolean = false
|
||||
protected set
|
||||
|
||||
fun isRoot() = parent == null
|
||||
}
|
||||
|
||||
class GraphicPackSectionNode : GraphicPackNode {
|
||||
constructor() : super(null) {
|
||||
this.parent = null
|
||||
}
|
||||
constructor() : super(null, null)
|
||||
|
||||
constructor(
|
||||
name: String,
|
||||
titleIdInstalled: Boolean,
|
||||
parent: GraphicPackSectionNode?
|
||||
) : super(name) {
|
||||
parent: GraphicPackSectionNode
|
||||
) : super(name, parent) {
|
||||
this.titleIdInstalled = titleIdInstalled
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
val parent: GraphicPackSectionNode?
|
||||
var enabledGraphicPacksCount: Int = 0
|
||||
private set
|
||||
var children: ArrayList<GraphicPackNode> = ArrayList()
|
||||
@@ -79,4 +85,32 @@ class GraphicPackSectionNode : GraphicPackNode {
|
||||
enabledGraphicPacksCount = max(0, enabledGraphicPacksCount + if (enabled) 1 else -1)
|
||||
parent?.updateEnabledCount(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GraphicPackDataNode(
|
||||
val id: Long,
|
||||
name: String,
|
||||
val path: String,
|
||||
enabled: Boolean,
|
||||
parent: GraphicPackSectionNode,
|
||||
) : GraphicPackNode(name, parent) {
|
||||
constructor(
|
||||
id: Long,
|
||||
name: String,
|
||||
path: String,
|
||||
enabled: Boolean,
|
||||
titleIdInstalled: Boolean,
|
||||
parentNode: GraphicPackSectionNode,
|
||||
) : this(id, name, path, enabled, parentNode) {
|
||||
this.titleIdInstalled = titleIdInstalled
|
||||
}
|
||||
|
||||
var enabled: Boolean = enabled
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
parent?.updateEnabledCount(value)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class GraphicPackViewModel(var graphicPackNode: GraphicPackNode? = null) : ViewModel()
|
||||
@@ -124,7 +124,7 @@ class GraphicPacksDownloader {
|
||||
graphicPacksTempDir.deleteRecursively()
|
||||
unzip(
|
||||
response.body.byteStream(),
|
||||
graphicPacksTempDir.path.toString()
|
||||
graphicPacksTempDir.path
|
||||
)
|
||||
graphicPacksTempDir.resolve("version.txt").writeText(version)
|
||||
val downloadedGraphicPacksDir =
|
||||
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import info.cemu.cemu.nativeinterface.NativeGameTitles
|
||||
import info.cemu.cemu.nativeinterface.NativeGraphicPacks
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class GraphicPacksListViewModel : ViewModel() {
|
||||
private val installedTitleIds = NativeGameTitles.getInstalledGamesTitleIds()
|
||||
private var rootNode = GraphicPackSectionNode()
|
||||
|
||||
private val _installedOnly = MutableStateFlow(installedTitleIds.size > 1)
|
||||
val installedOnly = _installedOnly.asStateFlow()
|
||||
fun setInstalledOnly(installedOnly: Boolean) {
|
||||
_installedOnly.value = installedOnly
|
||||
}
|
||||
|
||||
private val _downloadStatus = MutableStateFlow<GraphicPacksDownloadStatus?>(null)
|
||||
private suspend fun updateDownloadStatus(status: GraphicPacksDownloadStatus?) {
|
||||
_downloadStatus.first { it == null }
|
||||
_downloadStatus.value = status
|
||||
}
|
||||
|
||||
val downloadStatus = _downloadStatus.asStateFlow()
|
||||
|
||||
private var downloadJob: Job? = null
|
||||
fun downloadNewUpdate(context: Context) {
|
||||
if (_downloadStatus.value != null) return
|
||||
downloadJob = viewModelScope.launch {
|
||||
try {
|
||||
GraphicPacksDownloader.download(context) { updateDownloadStatus(it) }
|
||||
refreshGraphicPacks()
|
||||
} catch (_: Exception) {
|
||||
updateDownloadStatus(GraphicPacksDownloadStatus.ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadStatusRead() {
|
||||
_downloadStatus.value = null
|
||||
}
|
||||
|
||||
fun cancelDownload() {
|
||||
val oldDownloadJob = downloadJob ?: return
|
||||
downloadJob = null
|
||||
viewModelScope.launch {
|
||||
oldDownloadJob.cancelAndJoin()
|
||||
updateDownloadStatus(GraphicPacksDownloadStatus.CANCELED)
|
||||
}
|
||||
}
|
||||
|
||||
private val _filterText = MutableStateFlow("")
|
||||
val filterText: StateFlow<String> = _filterText
|
||||
fun setFilterText(filterText: String) {
|
||||
_filterText.value = filterText
|
||||
}
|
||||
|
||||
private val filterPattern = filterText.map { filterText ->
|
||||
if (filterText.isBlank()) {
|
||||
return@map null
|
||||
}
|
||||
return@map buildString {
|
||||
filterText.trim()
|
||||
.split(" ".toRegex())
|
||||
.forEach { append("(?=.*" + Pattern.quote(it) + ")") }
|
||||
append(".*")
|
||||
}.toPattern(Pattern.CASE_INSENSITIVE)
|
||||
}
|
||||
|
||||
private val _graphicPackDataNodes = MutableStateFlow<List<GraphicPackDataNode>>(emptyList())
|
||||
val graphicPackDataNodes: StateFlow<List<GraphicPackDataNode>> =
|
||||
combine(
|
||||
_graphicPackDataNodes,
|
||||
installedOnly,
|
||||
filterPattern
|
||||
) { graphicPackNodes, installedOnly, pattern ->
|
||||
if (!installedOnly && pattern == null) {
|
||||
return@combine graphicPackNodes
|
||||
}
|
||||
if (pattern != null) {
|
||||
return@combine graphicPackNodes.filter {
|
||||
pattern.matcher(it.path).matches() && (it.titleIdInstalled || !installedOnly)
|
||||
}
|
||||
}
|
||||
return@combine graphicPackNodes.filter { it.titleIdInstalled }
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
emptyList()
|
||||
)
|
||||
|
||||
private val _graphicPackNodes = MutableStateFlow<List<GraphicPackNode>>(emptyList())
|
||||
val graphicPackNodes: StateFlow<List<GraphicPackNode>> =
|
||||
installedOnly.combine(_graphicPackNodes) { installedOnly, graphicPackNodes ->
|
||||
if (installedOnly) {
|
||||
return@combine graphicPackNodes.filter { it.titleIdInstalled }
|
||||
}
|
||||
return@combine graphicPackNodes
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
emptyList()
|
||||
)
|
||||
|
||||
init {
|
||||
refreshGraphicPacks()
|
||||
}
|
||||
|
||||
private fun MutableList<GraphicPackDataNode>.fillWithDataNodes(graphicPackSectionNode: GraphicPackSectionNode): MutableList<GraphicPackDataNode> {
|
||||
for (node in graphicPackSectionNode.children) {
|
||||
when (node) {
|
||||
is GraphicPackSectionNode -> fillWithDataNodes(node)
|
||||
is GraphicPackDataNode -> add(node)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun refreshGraphicPacks() {
|
||||
rootNode = GraphicPackSectionNode().apply {
|
||||
NativeGraphicPacks.getGraphicPackBasicInfos().forEach {
|
||||
val hasTitleInstalled = it.titleIds.any { titleId -> titleId in installedTitleIds }
|
||||
addGraphicPackDataByTokens(it, hasTitleInstalled)
|
||||
}
|
||||
sort()
|
||||
}
|
||||
_graphicPackDataNodes.value =
|
||||
mutableListOf<GraphicPackDataNode>().fillWithDataNodes(rootNode)
|
||||
_graphicPackNodes.value = rootNode.children.toList()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val GraphicPacksDownloader = GraphicPacksDownloader()
|
||||
}
|
||||
}
|
||||
+5
-81
@@ -1,93 +1,17 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.viewmodel.MutableCreationExtras
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
object GraphicPacksRoute
|
||||
|
||||
private object GraphicPackRoutes {
|
||||
@Serializable
|
||||
object GraphicPacksRootSectionRoute
|
||||
|
||||
@Serializable
|
||||
object GraphicPackSectionScreenRoute
|
||||
|
||||
@Serializable
|
||||
object GraphicPackDataScreenRoute
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any, reified U : GraphicPackNode> NavGraphBuilder.composableNestedGraphicPacks(
|
||||
navController: NavController,
|
||||
noinline content: @Composable (AnimatedContentScope.(NavBackStackEntry, U) -> Unit),
|
||||
) {
|
||||
composable<T> { backStackEntry ->
|
||||
val previousBackStackEntry =
|
||||
navController.previousBackStackEntry ?: return@composable
|
||||
val graphicPackNode =
|
||||
viewModel<GraphicPackViewModel>(previousBackStackEntry).graphicPackNode
|
||||
if (graphicPackNode == null || graphicPackNode !is U) {
|
||||
return@composable
|
||||
}
|
||||
content(backStackEntry, graphicPackNode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun graphicPacksNavigate(navController: NavController, graphicPackNode: GraphicPackNode) {
|
||||
when (graphicPackNode) {
|
||||
is GraphicPackSectionNode -> navController.navigate(GraphicPackRoutes.GraphicPackSectionScreenRoute)
|
||||
is GraphicPackDataNode -> navController.navigate(GraphicPackRoutes.GraphicPackDataScreenRoute)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.graphicPacksNavigation(navController: NavHostController) {
|
||||
navigation<GraphicPacksRoute>(startDestination = GraphicPackRoutes.GraphicPacksRootSectionRoute) {
|
||||
composable<GraphicPackRoutes.GraphicPacksRootSectionRoute> { backStackEntry ->
|
||||
val graphicPackViewModel: GraphicPackViewModel = viewModel(backStackEntry)
|
||||
GraphicPacksRootSectionScreen(
|
||||
navigateBack = { navController.popBackStack() },
|
||||
graphicPackNodeNavigate = {
|
||||
graphicPackViewModel.graphicPackNode = it
|
||||
graphicPacksNavigate(navController, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
composableNestedGraphicPacks<GraphicPackRoutes.GraphicPackSectionScreenRoute, GraphicPackSectionNode>(
|
||||
navController
|
||||
) { backStackEntry, graphicPackNode ->
|
||||
val graphicPacksViewModel: GraphicPackViewModel = viewModel(backStackEntry)
|
||||
GraphicPacksSectionScreen(
|
||||
navigateBack = { navController.popBackStack() },
|
||||
graphicPackNodeNavigate = {
|
||||
graphicPacksViewModel.graphicPackNode = it
|
||||
graphicPacksNavigate(navController, it)
|
||||
},
|
||||
graphicPackSectionNode = graphicPackNode,
|
||||
)
|
||||
}
|
||||
composableNestedGraphicPacks<GraphicPackRoutes.GraphicPackDataScreenRoute, GraphicPackDataNode>(
|
||||
navController
|
||||
) { backStackEntry, graphicPackNode ->
|
||||
val graphicPackDataViewModel: GraphicPackDataViewModel = viewModel(
|
||||
viewModelStoreOwner = backStackEntry,
|
||||
factory = GraphicPackDataViewModel.Factory,
|
||||
extras = MutableCreationExtras().apply {
|
||||
set(GraphicPackDataViewModel.GRAPHIC_PACK_KEY, graphicPackNode)
|
||||
}
|
||||
)
|
||||
GraphicPackDataScreen(
|
||||
navigateBack = { navController.popBackStack() },
|
||||
graphicPackDataViewModel = graphicPackDataViewModel,
|
||||
)
|
||||
}
|
||||
composable<GraphicPacksRoute> {
|
||||
GraphicPacksScreen(
|
||||
navigateBack = { navController.popBackStack() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+102
-103
@@ -2,6 +2,7 @@
|
||||
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -62,28 +63,29 @@ import info.cemu.cemu.common.ui.components.SingleSelection
|
||||
import info.cemu.cemu.common.ui.localization.tr
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Composable
|
||||
fun GraphicPacksRootSectionScreen(
|
||||
fun GraphicPacksScreen(
|
||||
navigateBack: () -> Unit,
|
||||
graphicPackNodeNavigate: (GraphicPackNode) -> Unit,
|
||||
graphicPacksListViewModel: GraphicPacksListViewModel = viewModel(),
|
||||
graphicPacksViewModel: GraphicPacksViewModel = viewModel(),
|
||||
) {
|
||||
val graphicPackNodes by graphicPacksListViewModel.graphicPackNodes.collectAsState()
|
||||
val graphicPackDataNodes by graphicPacksListViewModel.graphicPackDataNodes.collectAsState()
|
||||
val query by graphicPacksListViewModel.filterText.collectAsState()
|
||||
val installedOnly by graphicPacksListViewModel.installedOnly.collectAsState()
|
||||
val graphicPackDataNodes by graphicPacksViewModel.graphicPackDataNodes.collectAsState()
|
||||
val query by graphicPacksViewModel.filterText.collectAsState()
|
||||
val installedOnly by graphicPacksViewModel.installedOnly.collectAsState()
|
||||
var showGraphicPackSearch by rememberSaveable { mutableStateOf(false) }
|
||||
val downloadStatus by graphicPacksListViewModel.downloadStatus.collectAsState()
|
||||
val downloadStatus by graphicPacksViewModel.downloadStatus.collectAsState()
|
||||
val snackbarScope = rememberCoroutineScope()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
var downloadDialogText by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val context = LocalContext.current
|
||||
val currentNodeState = graphicPacksViewModel.currentNode.collectAsState()
|
||||
val currentNode = currentNodeState.value
|
||||
val graphicPackDataState = graphicPacksViewModel.currentDataGraphicPack.collectAsState()
|
||||
val graphicPackData = graphicPackDataState.value
|
||||
|
||||
downloadStatus?.let { status ->
|
||||
downloadDialogText = downloadStatusToDialogTextString(status)
|
||||
LaunchedEffect(status) {
|
||||
graphicPacksListViewModel.downloadStatusRead()
|
||||
graphicPacksViewModel.downloadStatusRead()
|
||||
|
||||
val downloadNotificationText =
|
||||
downloadStatusToNotificationString(status) ?: return@LaunchedEffect
|
||||
@@ -94,26 +96,39 @@ fun GraphicPacksRootSectionScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
fun onNavigateBack() {
|
||||
|
||||
fun handleBack() {
|
||||
if (showGraphicPackSearch) {
|
||||
showGraphicPackSearch = false
|
||||
} else {
|
||||
navigateBack()
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentNodeState.value.isRoot()) {
|
||||
graphicPacksViewModel.navigateBack()
|
||||
return
|
||||
}
|
||||
|
||||
navigateBack()
|
||||
}
|
||||
|
||||
BackHandler(enabled = showGraphicPackSearch || !currentNodeState.value.isRoot()) {
|
||||
handleBack()
|
||||
}
|
||||
|
||||
ScreenContentLazy(
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
actions = {
|
||||
GraphicPacksRootSectionActions(
|
||||
showMainActions = !showGraphicPackSearch,
|
||||
onSearchClicked = {
|
||||
showGraphicPackSearch = true
|
||||
},
|
||||
onDownloadClicked = { graphicPacksListViewModel.downloadNewUpdate(context) },
|
||||
installedOnlyChecked = installedOnly,
|
||||
installedOnlyValueChange = graphicPacksListViewModel::setInstalledOnly,
|
||||
)
|
||||
if (currentNode.isRoot()) {
|
||||
GraphicPacksRootSectionActions(
|
||||
showMainActions = !showGraphicPackSearch,
|
||||
onSearchClicked = {
|
||||
showGraphicPackSearch = true
|
||||
},
|
||||
onDownloadClicked = { graphicPacksViewModel.downloadNewUpdate(context) },
|
||||
installedOnlyChecked = installedOnly,
|
||||
installedOnlyValueChange = graphicPacksViewModel::setInstalledOnly,
|
||||
)
|
||||
}
|
||||
},
|
||||
appBarTitle = {
|
||||
Box(
|
||||
@@ -121,35 +136,47 @@ fun GraphicPacksRootSectionScreen(
|
||||
.height(IntrinsicSize.Min)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
if (showGraphicPackSearch) {
|
||||
if (showGraphicPackSearch && currentNode.isRoot()) {
|
||||
SearchToolbarInput(
|
||||
value = query,
|
||||
onValueChange = graphicPacksListViewModel::setFilterText,
|
||||
onValueChange = graphicPacksViewModel::setFilterText,
|
||||
hint = tr("Search graphic packs"),
|
||||
)
|
||||
} else {
|
||||
DefaultAppBarTitle(tr("Graphic packs"))
|
||||
DefaultAppBarTitle(currentNode.name ?: tr("Graphic packs"))
|
||||
}
|
||||
}
|
||||
},
|
||||
navigateBack = ::onNavigateBack,
|
||||
navigateBack = ::handleBack,
|
||||
) {
|
||||
if (showGraphicPackSearch) {
|
||||
if (showGraphicPackSearch && currentNode.isRoot()) {
|
||||
graphicPackDataSearchItems(
|
||||
nodes = graphicPackDataNodes,
|
||||
onClick = graphicPackNodeNavigate,
|
||||
onClick = { graphicPacksViewModel.navigateTo(it) },
|
||||
)
|
||||
} else {
|
||||
return@ScreenContentLazy
|
||||
}
|
||||
|
||||
if (currentNode is GraphicPackSectionNode) {
|
||||
graphicPackSectionItems(
|
||||
nodes = graphicPackNodes,
|
||||
onClick = graphicPackNodeNavigate,
|
||||
installedOnly = installedOnly,
|
||||
nodes = currentNode.children,
|
||||
onClick = { graphicPacksViewModel.navigateTo(it) },
|
||||
)
|
||||
}
|
||||
|
||||
if (graphicPackData != null) {
|
||||
graphicPackDataNodeItem(
|
||||
graphicPacksViewModel = graphicPacksViewModel,
|
||||
graphicPackData = graphicPackData
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
if (downloadDialogText != null) {
|
||||
GraphicPacksDownloadDialog(
|
||||
onCancelRequest = {
|
||||
graphicPacksListViewModel.cancelDownload()
|
||||
graphicPacksViewModel.cancelDownload()
|
||||
},
|
||||
text = downloadDialogText!!,
|
||||
)
|
||||
@@ -173,7 +200,7 @@ private fun downloadStatusToNotificationString(downloadStatus: GraphicPacksDownl
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPacksDownloadDialog(
|
||||
private fun GraphicPacksDownloadDialog(
|
||||
onCancelRequest: () -> Unit,
|
||||
text: String,
|
||||
) {
|
||||
@@ -206,7 +233,7 @@ fun GraphicPacksDownloadDialog(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPacksRootSectionActions(
|
||||
private fun GraphicPacksRootSectionActions(
|
||||
showMainActions: Boolean,
|
||||
onSearchClicked: () -> Unit,
|
||||
onDownloadClicked: () -> Unit,
|
||||
@@ -261,7 +288,7 @@ fun GraphicPacksRootSectionActions(
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.graphicPackDataSearchItems(
|
||||
private fun LazyListScope.graphicPackDataSearchItems(
|
||||
nodes: List<GraphicPackDataNode>,
|
||||
onClick: (GraphicPackDataNode) -> Unit,
|
||||
) {
|
||||
@@ -291,80 +318,52 @@ fun LazyListScope.graphicPackDataSearchItems(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPacksSectionScreen(
|
||||
navigateBack: () -> Unit,
|
||||
graphicPackNodeNavigate: (GraphicPackNode) -> Unit,
|
||||
graphicPackSectionNode: GraphicPackSectionNode,
|
||||
private fun LazyListScope.graphicPackDataNodeItem(
|
||||
graphicPacksViewModel: GraphicPacksViewModel,
|
||||
graphicPackData: GraphicPackData
|
||||
) {
|
||||
val appBarText = graphicPackSectionNode.name ?: tr("Graphic packs")
|
||||
ScreenContentLazy(
|
||||
appBarText = appBarText,
|
||||
navigateBack = navigateBack,
|
||||
) {
|
||||
graphicPackSectionItems(
|
||||
nodes = graphicPackSectionNode.children,
|
||||
onClick = graphicPackNodeNavigate,
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(text = tr("Enabled"))
|
||||
Switch(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
checked = graphicPackData.active,
|
||||
onCheckedChange = graphicPacksViewModel::setCurrentGraphicPackActive,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = graphicPackData.description
|
||||
)
|
||||
}
|
||||
items(items = graphicPackData.presets) {
|
||||
SingleSelection(
|
||||
modifier = Modifier.animateItem(),
|
||||
label = it.category ?: tr("Active preset"),
|
||||
choices = it.choices,
|
||||
choice = it.activeChoice,
|
||||
onChoiceChanged = { activePreset ->
|
||||
graphicPacksViewModel.setCurrentGraphicPackActivePreset(
|
||||
it.index,
|
||||
activePreset
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPackDataScreen(
|
||||
navigateBack: () -> Unit,
|
||||
graphicPackDataViewModel: GraphicPackDataViewModel,
|
||||
) {
|
||||
val appBarText = graphicPackDataViewModel.name ?: tr("Graphic packs")
|
||||
val enabled by graphicPackDataViewModel.enabled.collectAsState()
|
||||
val presets by graphicPackDataViewModel.presets.collectAsState()
|
||||
|
||||
ScreenContentLazy(
|
||||
appBarText = appBarText,
|
||||
navigateBack = navigateBack,
|
||||
) {
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(text = tr("Enabled"))
|
||||
Switch(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
checked = enabled,
|
||||
onCheckedChange = graphicPackDataViewModel::setEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
text = graphicPackDataViewModel.description
|
||||
)
|
||||
}
|
||||
items(items = presets) {
|
||||
SingleSelection(
|
||||
modifier = Modifier.animateItem(),
|
||||
label = it.category ?: tr("Active preset"),
|
||||
choices = it.presets,
|
||||
choice = it.activePreset,
|
||||
onChoiceChanged = { activePreset ->
|
||||
graphicPackDataViewModel.setActivePreset(
|
||||
it.index,
|
||||
activePreset
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun LazyListScope.graphicPackSectionItems(
|
||||
nodes: List<GraphicPackNode>,
|
||||
installedOnly: Boolean,
|
||||
onClick: (GraphicPackNode) -> Unit,
|
||||
) {
|
||||
items(
|
||||
items = nodes,
|
||||
items = if (installedOnly) nodes.filter { it.titleIdInstalled } else nodes,
|
||||
) {
|
||||
GraphicPackListItem(
|
||||
label = it.name,
|
||||
@@ -380,7 +379,7 @@ private fun LazyListScope.graphicPackSectionItems(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPackDataListItemIcon(isEnabled: Boolean) {
|
||||
private fun GraphicPackDataListItemIcon(isEnabled: Boolean) {
|
||||
GraphicPackListItemIcon(
|
||||
painter = painterResource(R.drawable.ic_package_2),
|
||||
showExtraInfo = isEnabled,
|
||||
@@ -397,7 +396,7 @@ fun GraphicPackDataListItemIcon(isEnabled: Boolean) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPackSectionListItemIcon(numberOfEnabledPacks: Int) {
|
||||
private fun GraphicPackSectionListItemIcon(numberOfEnabledPacks: Int) {
|
||||
GraphicPackListItemIcon(
|
||||
painter = painterResource(R.drawable.ic_lists),
|
||||
showExtraInfo = numberOfEnabledPacks > 0,
|
||||
@@ -414,7 +413,7 @@ fun GraphicPackSectionListItemIcon(numberOfEnabledPacks: Int) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPackListItemIcon(
|
||||
private fun GraphicPackListItemIcon(
|
||||
painter: Painter,
|
||||
showExtraInfo: Boolean,
|
||||
extraInfoContent: @Composable () -> Unit,
|
||||
@@ -434,7 +433,7 @@ fun GraphicPackListItemIcon(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GraphicPackListItem(
|
||||
private fun GraphicPackListItem(
|
||||
label: String?,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier,
|
||||
@@ -0,0 +1,274 @@
|
||||
package info.cemu.cemu.graphicpacks
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import info.cemu.cemu.nativeinterface.NativeGameTitles
|
||||
import info.cemu.cemu.nativeinterface.NativeGraphicPacks
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.regex.Pattern
|
||||
|
||||
data class Preset(
|
||||
val index: Int,
|
||||
val category: String?,
|
||||
val activeChoice: String,
|
||||
val choices: List<String>,
|
||||
) {
|
||||
companion object {
|
||||
fun fromNativeGraphicPack(graphicPack: NativeGraphicPacks.GraphicPack): List<Preset> {
|
||||
return graphicPack.presets.mapIndexed { index, preset ->
|
||||
Preset(
|
||||
index = index,
|
||||
category = preset.category,
|
||||
activeChoice = preset.activePreset,
|
||||
choices = preset.presets,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
data class GraphicPackData(
|
||||
val description: String,
|
||||
val active: Boolean,
|
||||
val presets: List<Preset>,
|
||||
)
|
||||
|
||||
private fun MutableList<GraphicPackDataNode>.fillWithDataNodes(graphicPackSectionNode: GraphicPackSectionNode): MutableList<GraphicPackDataNode> {
|
||||
for (node in graphicPackSectionNode.children) {
|
||||
when (node) {
|
||||
is GraphicPackSectionNode -> fillWithDataNodes(node)
|
||||
is GraphicPackDataNode -> add(node)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
class GraphicPacksViewModel : ViewModel() {
|
||||
private var rootNode = GraphicPackSectionNode()
|
||||
|
||||
val installedTitleIds = NativeGameTitles.getInstalledGamesTitleIds()
|
||||
|
||||
private val _installedOnly = MutableStateFlow(installedTitleIds.size > 1)
|
||||
val installedOnly = _installedOnly.asStateFlow()
|
||||
fun setInstalledOnly(installedOnly: Boolean) {
|
||||
_installedOnly.value = installedOnly
|
||||
}
|
||||
|
||||
private val path = MutableStateFlow(listOf<GraphicPackNode>(rootNode))
|
||||
|
||||
val currentNode = path.map { it.last() }
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
rootNode
|
||||
)
|
||||
|
||||
private var currentNativeGraphicPack: NativeGraphicPacks.GraphicPack? = null
|
||||
private val _currentDataGraphicPack = MutableStateFlow<GraphicPackData?>(null)
|
||||
val currentDataGraphicPack = _currentDataGraphicPack.asStateFlow()
|
||||
|
||||
private fun setCurrentDataGraphicPack(graphicPackDataNode: GraphicPackDataNode) {
|
||||
val nativeGraphicPack = NativeGraphicPacks.getGraphicPack(graphicPackDataNode.id) ?: return
|
||||
currentNativeGraphicPack = nativeGraphicPack
|
||||
_currentDataGraphicPack.value = GraphicPackData(
|
||||
description = nativeGraphicPack.description,
|
||||
active = nativeGraphicPack.isActive(),
|
||||
presets = Preset.fromNativeGraphicPack(nativeGraphicPack)
|
||||
)
|
||||
}
|
||||
|
||||
private fun clearCurrentDataGraphicPack() {
|
||||
currentNativeGraphicPack = null
|
||||
_currentDataGraphicPack.value = null
|
||||
}
|
||||
|
||||
fun setCurrentGraphicPackActive(active: Boolean) {
|
||||
val dataNode = currentNode.value
|
||||
if (dataNode !is GraphicPackDataNode) {
|
||||
return
|
||||
}
|
||||
dataNode.enabled = active
|
||||
currentNativeGraphicPack?.setActive(active)
|
||||
_currentDataGraphicPack.value = _currentDataGraphicPack.value?.copy(active = active)
|
||||
}
|
||||
|
||||
private fun refreshCurrentGraphicPackPresets() {
|
||||
val nativeGraphicPack = currentNativeGraphicPack ?: return
|
||||
|
||||
val oldPresets = nativeGraphicPack.presets
|
||||
|
||||
nativeGraphicPack.reloadPresets()
|
||||
|
||||
if (oldPresets == nativeGraphicPack.presets) {
|
||||
return
|
||||
}
|
||||
|
||||
_currentDataGraphicPack.value = _currentDataGraphicPack.value?.copy(
|
||||
presets = Preset.fromNativeGraphicPack(nativeGraphicPack)
|
||||
)
|
||||
}
|
||||
|
||||
fun setCurrentGraphicPackActivePreset(index: Int, activePreset: String) {
|
||||
val nativeGraphicPack = currentNativeGraphicPack ?: return
|
||||
|
||||
var presets = _currentDataGraphicPack.value?.presets ?: return
|
||||
|
||||
presets = presets.toMutableList().apply {
|
||||
set(index, get(index).copy(activeChoice = activePreset))
|
||||
}
|
||||
|
||||
_currentDataGraphicPack.value = _currentDataGraphicPack.value?.copy(presets = presets)
|
||||
|
||||
nativeGraphicPack.presets[index].activePreset = activePreset
|
||||
|
||||
refreshCurrentGraphicPackPresets()
|
||||
}
|
||||
|
||||
|
||||
fun navigateBack() {
|
||||
val currentPath = path.value
|
||||
val lastNode = currentPath.last()
|
||||
|
||||
if (currentPath.size > 1) {
|
||||
path.value = currentPath.dropLast(1)
|
||||
}
|
||||
|
||||
if (lastNode is GraphicPackDataNode) {
|
||||
clearCurrentDataGraphicPack()
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateTo(node: GraphicPackNode) {
|
||||
val currentPath = path.value
|
||||
|
||||
if (node is GraphicPackSectionNode) {
|
||||
path.value += node
|
||||
return
|
||||
}
|
||||
|
||||
if (node !is GraphicPackDataNode) {
|
||||
return
|
||||
}
|
||||
|
||||
setCurrentDataGraphicPack(node)
|
||||
|
||||
if (currentPath.last() === node.parent) {
|
||||
path.value += node
|
||||
return
|
||||
}
|
||||
|
||||
val newPath = mutableListOf<GraphicPackNode>(node)
|
||||
var currentNode = node.parent
|
||||
while (currentNode != null) {
|
||||
newPath.add(currentNode)
|
||||
currentNode = currentNode.parent
|
||||
}
|
||||
|
||||
path.value = newPath.reversed()
|
||||
}
|
||||
|
||||
private val _downloadStatus = MutableStateFlow<GraphicPacksDownloadStatus?>(null)
|
||||
private suspend fun updateDownloadStatus(status: GraphicPacksDownloadStatus?) {
|
||||
_downloadStatus.first { it == null }
|
||||
_downloadStatus.value = status
|
||||
}
|
||||
|
||||
val downloadStatus = _downloadStatus.asStateFlow()
|
||||
|
||||
private var downloadJob: Job? = null
|
||||
fun downloadNewUpdate(context: Context) {
|
||||
if (_downloadStatus.value != null) return
|
||||
downloadJob = viewModelScope.launch {
|
||||
try {
|
||||
GraphicPacksDownloader.download(context) { updateDownloadStatus(it) }
|
||||
refreshGraphicPacks()
|
||||
} catch (_: Exception) {
|
||||
updateDownloadStatus(GraphicPacksDownloadStatus.ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadStatusRead() {
|
||||
_downloadStatus.value = null
|
||||
}
|
||||
|
||||
fun cancelDownload() {
|
||||
val oldDownloadJob = downloadJob ?: return
|
||||
downloadJob = null
|
||||
viewModelScope.launch {
|
||||
oldDownloadJob.cancelAndJoin()
|
||||
updateDownloadStatus(GraphicPacksDownloadStatus.CANCELED)
|
||||
}
|
||||
}
|
||||
|
||||
private val _filterText = MutableStateFlow("")
|
||||
val filterText: StateFlow<String> = _filterText
|
||||
fun setFilterText(filterText: String) {
|
||||
_filterText.value = filterText
|
||||
}
|
||||
|
||||
private val filterPattern = filterText.map { filterText ->
|
||||
if (filterText.isBlank()) {
|
||||
return@map null
|
||||
}
|
||||
return@map buildString {
|
||||
filterText.trim().split(" ".toRegex())
|
||||
.forEach { append("(?=.*" + Pattern.quote(it) + ")") }
|
||||
append(".*")
|
||||
}.toPattern(Pattern.CASE_INSENSITIVE)
|
||||
}
|
||||
|
||||
private val _graphicPackDataNodes = MutableStateFlow<List<GraphicPackDataNode>>(emptyList())
|
||||
val graphicPackDataNodes: StateFlow<List<GraphicPackDataNode>> = combine(
|
||||
_graphicPackDataNodes, installedOnly, filterPattern
|
||||
) { graphicPackNodes, installedOnly, pattern ->
|
||||
if (!installedOnly && pattern == null) {
|
||||
return@combine graphicPackNodes
|
||||
}
|
||||
if (pattern != null) {
|
||||
return@combine graphicPackNodes.filter {
|
||||
pattern.matcher(it.path).matches() && (it.titleIdInstalled || !installedOnly)
|
||||
}
|
||||
}
|
||||
return@combine graphicPackNodes.filter { it.titleIdInstalled }
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
emptyList()
|
||||
)
|
||||
|
||||
private fun refreshGraphicPacks() {
|
||||
rootNode = GraphicPackSectionNode().apply {
|
||||
NativeGraphicPacks.getGraphicPackBasicInfos().forEach {
|
||||
val hasTitleInstalled = it.titleIds.any { titleId -> titleId in installedTitleIds }
|
||||
addGraphicPackDataByTokens(it, hasTitleInstalled)
|
||||
}
|
||||
sort()
|
||||
}
|
||||
|
||||
path.value = listOf(rootNode)
|
||||
|
||||
_graphicPackDataNodes.value =
|
||||
mutableListOf<GraphicPackDataNode>().fillWithDataNodes(rootNode)
|
||||
}
|
||||
|
||||
init {
|
||||
refreshGraphicPacks()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val GraphicPacksDownloader = GraphicPacksDownloader()
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import androidx.annotation.Keep
|
||||
object NativeAccount {
|
||||
const val MAX_ACCOUNT_COUNT = 12
|
||||
const val MIN_ACCOUNT_COUNT = 1
|
||||
const val MIN_PERSISTENT_ID: UInt = 2147483649u
|
||||
const val MIN_PERSISTENT_ID: UInt = 0x80000001u
|
||||
const val DEFAULT_MII_NAME = "default"
|
||||
|
||||
object AccountGender {
|
||||
@@ -54,16 +54,22 @@ object NativeAccount {
|
||||
|
||||
@Keep
|
||||
sealed interface OnlineValidationError
|
||||
|
||||
@Keep
|
||||
class MissingOTP : OnlineValidationError
|
||||
|
||||
@Keep
|
||||
class CorruptedOTP : OnlineValidationError
|
||||
|
||||
@Keep
|
||||
class MissingSEEPROM : OnlineValidationError
|
||||
|
||||
@Keep
|
||||
class CorruptedSEEPROM : OnlineValidationError
|
||||
|
||||
@Keep
|
||||
data class MissingFile(val file: String) : OnlineValidationError
|
||||
|
||||
@Keep
|
||||
data class AccountError(val accountError: Int) : OnlineValidationError
|
||||
|
||||
|
||||
@@ -201,13 +201,13 @@ class TitleListViewModel : ViewModel() {
|
||||
val titleInstallProgress = installUseCase.progress
|
||||
|
||||
fun installQueuedTitle(context: Context, callback: (InstallResult) -> Unit) {
|
||||
val queued = _queuedTitleToInstall.value ?: return
|
||||
val (titleUri, titleExistsStatus) = _queuedTitleToInstall.value ?: return
|
||||
_queuedTitleToInstall.value = null
|
||||
|
||||
installUseCase.install(
|
||||
context = context,
|
||||
titleUri = queued.first,
|
||||
targetLocation = queued.second.targetLocation,
|
||||
titleUri = titleUri,
|
||||
targetLocation = titleExistsStatus.targetLocation,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
@@ -246,6 +246,7 @@ class TitleListViewModel : ViewModel() {
|
||||
uri: Uri,
|
||||
onResult: (CompressResult) -> Unit
|
||||
) {
|
||||
_queuedTitleToCompress.value = null
|
||||
compressUseCase.compress(context, uri, onResult)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -107,6 +108,7 @@ fun TitleManagerScreen(
|
||||
when (it) {
|
||||
InstallResult.ERROR -> showNotificationMessage(tr("Error installing"))
|
||||
InstallResult.FINISHED -> showNotificationMessage(tr("Finished installing"))
|
||||
InstallResult.NOT_ENOUGH_SPACE -> showNotificationMessage(tr("Not enough space"))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -240,7 +242,7 @@ fun TitleManagerScreen(
|
||||
|
||||
@Composable
|
||||
private fun TitleCompressProgressDialog(bytesWritten: Long?, onCancel: () -> Unit) {
|
||||
var showCancelConfirmDialog by remember { mutableStateOf(false) }
|
||||
var showCancelConfirmDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
AlertDialog(
|
||||
title = { Text(tr("Compressing title")) },
|
||||
@@ -345,7 +347,7 @@ private fun TitleInstallProgressDialog(
|
||||
progress: Pair<Long, Long>?,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
var showCancelConfirmDialog by remember { mutableStateOf(false) }
|
||||
var showCancelConfirmDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
AlertDialog(
|
||||
title = { Text(tr("Installing title")) },
|
||||
@@ -504,7 +506,7 @@ private fun <T : Enum<T>> FilterRow(
|
||||
valueToLabel: @Composable (T) -> String,
|
||||
onToggle: (T) -> Unit
|
||||
) {
|
||||
var showOptions by remember { mutableStateOf(false) }
|
||||
var showOptions by rememberSaveable { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
@@ -561,7 +563,7 @@ private fun TitleEntryListItem(
|
||||
onDeleteRequest: () -> Unit,
|
||||
onCompressRequested: () -> Unit,
|
||||
) {
|
||||
var showDeleteConfirmationDialog by remember { mutableStateOf(false) }
|
||||
var showDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
@@ -572,7 +574,7 @@ private fun TitleEntryListItem(
|
||||
.animateContentSize()
|
||||
.padding(8.dp),
|
||||
) {
|
||||
var showTitleInfo by remember { mutableStateOf(false) }
|
||||
var showTitleInfo by rememberSaveable { mutableStateOf(false) }
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
@@ -622,7 +624,7 @@ private fun TitleDropDownMenu(
|
||||
onDeleteClicked: () -> Unit,
|
||||
onCompressClicked: () -> Unit,
|
||||
) {
|
||||
var expandMenu by remember { mutableStateOf(false) }
|
||||
var expandMenu by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
@Composable
|
||||
fun DropdownMenuItem(text: String, onClick: () -> Unit) {
|
||||
|
||||
+2
@@ -29,6 +29,8 @@ class CompressTitleUseCase(private val scope: CoroutineScope) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
progressJob?.cancelAndJoin()
|
||||
NativeGameTitles.cancelTitleCompression()
|
||||
_inProgress.value = false
|
||||
_progress.value = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -29,6 +29,7 @@ import kotlin.random.nextUInt
|
||||
enum class InstallResult{
|
||||
ERROR,
|
||||
FINISHED,
|
||||
NOT_ENOUGH_SPACE,
|
||||
}
|
||||
|
||||
private sealed class DirEntry {
|
||||
@@ -87,7 +88,7 @@ class InstallTitleUseCase(
|
||||
)
|
||||
|
||||
if (totalSize > mlcPath.toFile().freeSpace) {
|
||||
callback(InstallResult.ERROR)
|
||||
callback(InstallResult.NOT_ENOUGH_SPACE)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user