merged 2.0.3 Layout into 2.0.4

This commit is contained in:
BeZide93
2025-09-25 17:10:02 -05:00
committed by KeatonTheBot
parent 48ba1cc9ed
commit be176fd367
5 changed files with 129 additions and 146 deletions
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature <uses-feature
android:name="android.hardware.audio.output" android:name="android.hardware.audio.output"
@@ -49,6 +50,12 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<!--
<intent-filter>
<action android:name="org.kenji.android.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
-->
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.kenjinx.android.fileprovider" android:authorities="org.kenjinx.android.fileprovider"
@@ -60,7 +67,7 @@
</provider> </provider>
<provider <provider
android:name=".providers.DocumentProvider" android:name=".providers.DocumentProvider"
android:authorities="org.kenjinx.android.providers" android:authorities="${applicationId}.providers"
android:exported="true" android:exported="true"
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"> android:permission="android.permission.MANAGE_DOCUMENTS">
@@ -107,11 +107,6 @@ class HomeViewModel(
for(game in loadedCache) for(game in loadedCache)
{ {
game.getGameInfo() game.getGameInfo()
if(game.isUnknown())
{
loadedCache.remove(game);
}
} }
} finally { } finally {
isLoading.value = false isLoading.value = false
@@ -49,12 +49,12 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -66,6 +66,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@@ -112,15 +113,13 @@ class HomeViews {
var isFabVisible by remember { mutableStateOf(true) } var isFabVisible by remember { mutableStateOf(true) }
val isNavigating = remember { mutableStateOf(false) } val isNavigating = remember { mutableStateOf(false) }
val context = LocalContext.current
val nestedScrollConnection = remember { val nestedScrollConnection = remember {
object : NestedScrollConnection { object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (available.y < -1) { if (available.y < -1) isFabVisible = false
isFabVisible = false if (available.y > 1) isFabVisible = true
}
if (available.y > 1) {
isFabVisible = true
}
return Offset.Zero return Offset.Zero
} }
} }
@@ -162,7 +161,6 @@ class HomeViews {
.clickable { .clickable {
if (!isNavigating.value) { if (!isNavigating.value) {
isNavigating.value = true isNavigating.value = true
val currentRoute = navController?.currentDestination?.route val currentRoute = navController?.currentDestination?.route
if (currentRoute != "user") { if (currentRoute != "user") {
navController?.navigate("user") { navController?.navigate("user") {
@@ -170,7 +168,6 @@ class HomeViews {
restoreState = true restoreState = true
} }
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
delay(500) delay(500)
isNavigating.value = false isNavigating.value = false
@@ -181,7 +178,6 @@ class HomeViews {
) { ) {
if (refreshUser && viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) { if (refreshUser && viewModel.mainViewModel?.userViewModel?.openedUser?.userPicture?.isNotEmpty() == true) {
val pic = viewModel.mainViewModel.userViewModel.openedUser.userPicture val pic = viewModel.mainViewModel.userViewModel.openedUser.userPicture
Image( Image(
bitmap = BitmapFactory.decodeByteArray( bitmap = BitmapFactory.decodeByteArray(
pic, pic,
@@ -201,11 +197,12 @@ class HomeViews {
) )
} }
} }
// Settings
IconButton( IconButton(
onClick = { onClick = {
if (!isNavigating.value) { if (!isNavigating.value) {
isNavigating.value = true isNavigating.value = true
val currentRoute = navController?.currentDestination?.route val currentRoute = navController?.currentDestination?.route
if (currentRoute != "settings") { if (currentRoute != "settings") {
navController?.navigate("settings") { navController?.navigate("settings") {
@@ -213,7 +210,6 @@ class HomeViews {
restoreState = true restoreState = true
} }
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
delay(500) delay(500)
isNavigating.value = false isNavigating.value = false
@@ -229,33 +225,21 @@ class HomeViews {
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
) { ) {
Icon( Icon(Icons.Filled.Settings, contentDescription = "Settings")
Icons.Filled.Settings,
contentDescription = "Settings"
)
} }
} }
OutlinedTextField( OutlinedTextField(
value = query.value, value = query.value,
onValueChange = { onValueChange = { query.value = it },
query.value = it
},
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(56.dp), .height(56.dp),
placeholder = { placeholder = {
Text( Text("Search...", modifier = Modifier.padding(bottom = 4.dp))
"Search...",
modifier = Modifier.padding(bottom = 4.dp)
)
},
leadingIcon = {
Icon(
Icons.Filled.Search,
contentDescription = "Search"
)
}, },
leadingIcon = { Icon(Icons.Filled.Search, contentDescription = "Search") },
singleLine = true, singleLine = true,
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors( colors = TextFieldDefaults.outlinedTextFieldColors(
@@ -289,11 +273,9 @@ class HomeViews {
viewModel.filter(query.value) viewModel.filter(query.value)
if (!isPreview) { if (!isPreview) {
var settings = QuickSettings(viewModel.activity!!) val settings = QuickSettings(viewModel.activity!!)
if (isLoading.value) { if (isLoading.value) {
Box(modifier = Modifier.fillMaxSize()) Box(modifier = Modifier.fillMaxSize()) {
{
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier modifier = Modifier
.width(64.dp) .width(64.dp)
@@ -304,8 +286,7 @@ class HomeViews {
} }
} else { } else {
if (settings.isGrid) { if (settings.isGrid) {
val size = val size = GridImageSize / Resources.getSystem().displayMetrics.density
GridImageSize / Resources.getSystem().displayMetrics.density
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = (size + 4).dp), columns = GridCells.Adaptive(minSize = (size + 4).dp),
modifier = Modifier modifier = Modifier
@@ -318,8 +299,7 @@ class HomeViews {
it.titleName?.apply { it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim() if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase(Locale.getDefault()) .isEmpty() || this.lowercase(Locale.getDefault())
.contains(query.value)) .contains(query.value))) {
)
GridGameItem( GridGameItem(
it, it,
viewModel, viewModel,
@@ -331,16 +311,14 @@ class HomeViews {
} }
} }
} }
}
} else { } else {
LazyColumn(Modifier.fillMaxSize()) { LazyColumn(Modifier.fillMaxSize()) {
items(list) { items(list) {
it.titleName?.apply { it.titleName?.apply {
if (this.isNotEmpty() && (query.value.trim() if (this.isNotEmpty() && (query.value.trim()
.isEmpty() || this.lowercase( .isEmpty() || this.lowercase(Locale.getDefault())
Locale.getDefault() .contains(query.value))) {
)
.contains(query.value))
)
Box(modifier = Modifier.animateItemPlacement()) { Box(modifier = Modifier.animateItemPlacement()) {
ListGameItem( ListGameItem(
it, it,
@@ -359,6 +337,7 @@ class HomeViews {
} }
} }
} }
}
SimpleAlertDialog.Loading(showDialog = showLoading) SimpleAlertDialog.Loading(showDialog = showLoading)
SimpleAlertDialog.Custom( SimpleAlertDialog.Custom(
@@ -383,13 +362,14 @@ class HomeViews {
if (viewModel.mainViewModel?.loadGameModel?.value != null) if (viewModel.mainViewModel?.loadGameModel?.value != null)
LaunchedEffect(viewModel.mainViewModel.loadGameModel.value) { LaunchedEffect(viewModel.mainViewModel.loadGameModel.value) {
if (viewModel.mainViewModel.bootPath.value == "gameItem_${viewModel.mainViewModel.loadGameModel.value!!.titleName}") { if (viewModel.mainViewModel.bootPath.value ==
"gameItem_${viewModel.mainViewModel.loadGameModel.value!!.titleName}"
) {
viewModel.mainViewModel.bootPath.value = null viewModel.mainViewModel.bootPath.value = null
thread { thread {
showLoading.value = true showLoading.value = true
val success = val success = viewModel.mainViewModel.loadGame(
viewModel.mainViewModel.loadGame(
viewModel.mainViewModel.loadGameModel.value!!, viewModel.mainViewModel.loadGameModel.value!!,
true, true,
viewModel.mainViewModel.forceNceAndPptc.value viewModel.mainViewModel.forceNceAndPptc.value
@@ -400,8 +380,7 @@ class HomeViews {
} }
} else { } else {
if (success == -2) if (success == -2)
showError.value = showError.value = "Error loading update. Please re-add update file"
"Error loading update. Please re-add update file"
viewModel.mainViewModel.loadGameModel.value!!.close() viewModel.mainViewModel.loadGameModel.value!!.close()
} }
showLoading.value = false showLoading.value = false
@@ -421,8 +400,9 @@ class HomeViews {
if (viewModel.mainViewModel?.selected != null) { if (viewModel.mainViewModel?.selected != null) {
thread { thread {
showLoading.value = true showLoading.value = true
val success = val success = viewModel.mainViewModel.loadGame(
viewModel.mainViewModel.loadGame(viewModel.mainViewModel.selected!!) viewModel.mainViewModel.selected!!
)
if (success == 1) { if (success == 1) {
launchOnUiThread { launchOnUiThread {
viewModel.mainViewModel.navigateToGame() viewModel.mainViewModel.navigateToGame()
@@ -444,53 +424,54 @@ class HomeViews {
} }
val showAppMenu = remember { mutableStateOf(false) } val showAppMenu = remember { mutableStateOf(false) }
Box { Box {
IconButton(onClick = { IconButton(onClick = { showAppMenu.value = true }) {
showAppMenu.value = true Icon(Icons.Filled.Menu, contentDescription = "Menu")
}) {
Icon(
Icons.Filled.Menu,
contentDescription = "Menu"
)
} }
DropdownMenu( DropdownMenu(
expanded = showAppMenu.value, expanded = showAppMenu.value,
onDismissRequest = { showAppMenu.value = false }) { onDismissRequest = { showAppMenu.value = false }
DropdownMenuItem(text = { ) {
Text(text = "Clear PPTC Cache") DropdownMenuItem(
}, onClick = { text = { Text(text = "Clear PPTC Cache") },
onClick = {
showAppMenu.value = false showAppMenu.value = false
viewModel.mainViewModel?.clearPptcCache( viewModel.mainViewModel?.clearPptcCache(
viewModel.mainViewModel.selected?.titleId ?: "" viewModel.mainViewModel.selected?.titleId ?: ""
) )
}) }
DropdownMenuItem(text = { )
Text(text = "Purge Shader Cache") DropdownMenuItem(
}, onClick = { text = { Text(text = "Purge Shader Cache") },
onClick = {
showAppMenu.value = false showAppMenu.value = false
viewModel.mainViewModel?.purgeShaderCache( viewModel.mainViewModel?.purgeShaderCache(
viewModel.mainViewModel.selected?.titleId ?: "" viewModel.mainViewModel.selected?.titleId ?: ""
) )
}) }
DropdownMenuItem(text = { )
Text(text = "Delete All Cache") DropdownMenuItem(
}, onClick = { text = { Text(text = "Delete All Cache") },
onClick = {
showAppMenu.value = false showAppMenu.value = false
viewModel.mainViewModel?.deleteCache( viewModel.mainViewModel?.deleteCache(
viewModel.mainViewModel.selected?.titleId ?: "" viewModel.mainViewModel.selected?.titleId ?: ""
) )
}) }
DropdownMenuItem(text = { )
Text(text = "Manage Updates") DropdownMenuItem(
}, onClick = { text = { Text(text = "Manage Updates") },
onClick = {
showAppMenu.value = false showAppMenu.value = false
openTitleUpdateDialog.value = true openTitleUpdateDialog.value = true
}) }
DropdownMenuItem(text = { )
Text(text = "Manage DLC") DropdownMenuItem(
}, onClick = { text = { Text(text = "Manage DLC") },
onClick = {
showAppMenu.value = false showAppMenu.value = false
openDlcDialog.value = true openDlcDialog.value = true
}) }
)
} }
} }
} }
@@ -513,9 +494,7 @@ class HomeViews {
selectedModel: MutableState<GameModel?>, selectedModel: MutableState<GameModel?>,
showError: MutableState<String> showError: MutableState<String>
) { ) {
remember { remember { selectedModel }
selectedModel
}
val color = val color =
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
@@ -530,19 +509,17 @@ class HomeViews {
onClick = { onClick = {
if (viewModel.mainViewModel?.selected != null) { if (viewModel.mainViewModel?.selected != null) {
showAppActions.value = false showAppActions.value = false
viewModel.mainViewModel.apply { viewModel.mainViewModel.apply { selected = null }
selected = null
}
selectedModel.value = null selectedModel.value = null
} else if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro) { } else if (gameModel.titleId.isNullOrEmpty()
|| gameModel.titleId != "0000000000000000"
|| gameModel.type == FileType.Nro
) {
thread { thread {
showLoading.value = true showLoading.value = true
val success = val success = viewModel.mainViewModel?.loadGame(gameModel) ?: false
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success == 1) { if (success == 1) {
launchOnUiThread { launchOnUiThread { viewModel.mainViewModel?.navigateToGame() }
viewModel.mainViewModel?.navigateToGame()
}
} else { } else {
if (success == -2) if (success == -2)
showError.value = showError.value =
@@ -557,7 +534,8 @@ class HomeViews {
viewModel.mainViewModel?.selected = gameModel viewModel.mainViewModel?.selected = gameModel
showAppActions.value = true showAppActions.value = true
selectedModel.value = gameModel selectedModel.value = gameModel
}) }
)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -566,22 +544,21 @@ class HomeViews {
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
Row { Row {
if (!gameModel.titleId.isNullOrEmpty() && (gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro)) { if (!gameModel.titleId.isNullOrEmpty()
&& (gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro)
) {
if (gameModel.icon?.isNotEmpty() == true) { if (gameModel.icon?.isNotEmpty() == true) {
val pic = decoder.decode(gameModel.icon) val pic = decoder.decode(gameModel.icon)
val size = val size = ListImageSize / Resources.getSystem().displayMetrics.density
ListImageSize / Resources.getSystem().displayMetrics.density
Image( Image(
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size) bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size).asImageBitmap(),
.asImageBitmap(),
contentDescription = gameModel.titleName + " icon", contentDescription = gameModel.titleName + " icon",
modifier = Modifier modifier = Modifier
.padding(end = 8.dp) .padding(end = 8.dp)
.width(size.roundToInt().dp) .width(size.roundToInt().dp)
.height(size.roundToInt().dp) .height(size.roundToInt().dp)
) )
} else if (gameModel.type == FileType.Nro) } else if (gameModel.type == FileType.Nro) NROIcon()
NROIcon()
else NotAvailableIcon() else NotAvailableIcon()
} else NotAvailableIcon() } else NotAvailableIcon()
Column { Column {
@@ -608,9 +585,7 @@ class HomeViews {
selectedModel: MutableState<GameModel?>, selectedModel: MutableState<GameModel?>,
showError: MutableState<String> showError: MutableState<String>
) { ) {
remember { remember { selectedModel }
selectedModel
}
val color = val color =
if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface if (selectedModel.value == gameModel) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface
@@ -625,19 +600,17 @@ class HomeViews {
onClick = { onClick = {
if (viewModel.mainViewModel?.selected != null) { if (viewModel.mainViewModel?.selected != null) {
showAppActions.value = false showAppActions.value = false
viewModel.mainViewModel.apply { viewModel.mainViewModel.apply { selected = null }
selected = null
}
selectedModel.value = null selectedModel.value = null
} else if (gameModel.titleId.isNullOrEmpty() || gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro) { } else if (gameModel.titleId.isNullOrEmpty()
|| gameModel.titleId != "0000000000000000"
|| gameModel.type == FileType.Nro
) {
thread { thread {
showLoading.value = true showLoading.value = true
val success = val success = viewModel.mainViewModel?.loadGame(gameModel) ?: false
viewModel.mainViewModel?.loadGame(gameModel) ?: false
if (success == 1) { if (success == 1) {
launchOnUiThread { launchOnUiThread { viewModel.mainViewModel?.navigateToGame() }
viewModel.mainViewModel?.navigateToGame()
}
} else { } else {
if (success == -2) if (success == -2)
showError.value = showError.value =
@@ -652,23 +625,24 @@ class HomeViews {
viewModel.mainViewModel?.selected = gameModel viewModel.mainViewModel?.selected = gameModel
showAppActions.value = true showAppActions.value = true
selectedModel.value = gameModel selectedModel.value = gameModel
}) }
)
) { ) {
Column(modifier = Modifier.padding(4.dp)) { Column(modifier = Modifier.padding(4.dp)) {
if (!gameModel.titleId.isNullOrEmpty() && (gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro)) { if (!gameModel.titleId.isNullOrEmpty()
&& (gameModel.titleId != "0000000000000000" || gameModel.type == FileType.Nro)
) {
if (gameModel.icon?.isNotEmpty() == true) { if (gameModel.icon?.isNotEmpty() == true) {
val pic = decoder.decode(gameModel.icon) val pic = decoder.decode(gameModel.icon)
Image( Image(
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size) bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.size).asImageBitmap(),
.asImageBitmap(),
contentDescription = gameModel.titleName + " icon", contentDescription = gameModel.titleName + " icon",
modifier = Modifier modifier = Modifier
.padding(0.dp) .padding(0.dp)
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.align(Alignment.CenterHorizontally) .align(Alignment.CenterHorizontally)
) )
} else if (gameModel.type == FileType.Nro) } else if (gameModel.type == FileType.Nro) NROIcon()
NROIcon()
else NotAvailableIcon() else NotAvailableIcon()
} else NotAvailableIcon() } else NotAvailableIcon()
Text( Text(
@@ -708,7 +682,6 @@ class HomeViews {
.height(size.roundToInt().dp) .height(size.roundToInt().dp)
) )
} }
} }
@Preview @Preview
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Dummy definition that contains no Android items.
The real Android flags come in values-v21/styles.xml -->
<style name="Theme.KenjinxAndroid.Transparent" parent="Theme.Material3.DayNight.NoActionBar"/>
</resources>
@@ -2,4 +2,5 @@
<resources> <resources>
<style name="Theme.KenjinxAndroid" parent="android:Theme.Material.Light.NoActionBar" /> <style name="Theme.KenjinxAndroid" parent="android:Theme.Material.Light.NoActionBar" />
</resources> </resources>