Compare commits

..
3 Commits
Author SHA1 Message Date
Illusion 9e582d3788 move mainactivity to root package 2023-07-14 19:37:46 +03:00
Illusion 4b84275f3a hardcode the updates HMAC key 2023-07-14 16:53:50 +03:00
Illusion bcd2591ba7 implement search 2023-07-14 13:00:12 +03:00
9 changed files with 123 additions and 154 deletions
+1 -1
View File
@@ -16,7 +16,7 @@
android:theme="@style/NPSTheme"
android:usesCleartextTraffic="true">
<activity
android:name=".activities.MainActivity"
android:name=".MainActivity"
android:configChanges="screenSize|screenLayout|keyboardHidden|orientation"
android:exported="true"
android:theme="@style/NPSTheme.Splash"
@@ -1,4 +1,4 @@
package com.illusionware.npsbrowser.activities
package com.illusionware.npsbrowser
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -28,6 +28,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Downloading
import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
@@ -84,8 +85,6 @@ import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.illusionware.npsbrowser.R
import com.illusionware.npsbrowser.Routes
import com.illusionware.npsbrowser.data.ItemLayout
import com.illusionware.npsbrowser.data.SettingsPreferences
import com.illusionware.npsbrowser.model.ConsoleType
@@ -189,15 +188,17 @@ fun HomePage(
navigateToDetailsScreen: (item: PackageItem) -> Unit,
settingsPrefs: SettingsPreferences,
settingsViewModel: SettingsViewModel,
packageListViewModel: PackageListViewModel = viewModel(
factory = PackageListViewModel.Factory
),
) {
var isSearchOpen by rememberSaveable { mutableStateOf(false)}
var searchQuery by rememberSaveable { mutableStateOf("")}
val searchBar = remember { FocusRequester() }
var layoutDialogOpen by remember { mutableStateOf(false) }
val context = LocalContext.current
val systemUiController = rememberSystemUiController()
val navbarScrimColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
val uiState = packageListViewModel.uiState.collectAsStateWithLifecycle().value
val tsvs = settingsViewModel.availableTsvs()
var selectedBarItem by rememberSaveable { mutableStateOf(ConsoleType.PSVITA) }
@@ -215,22 +216,26 @@ fun HomePage(
topBar = {
TopAppBar(
title = {
if (!isSearchOpen) {
if (!uiState.isSearchOpen) {
Text(text = stringResource(id = R.string.app_name))
}
},
actions = {
if (isSearchOpen) {
if (uiState.isSearchOpen) {
NPSIconButton(
tooltip = "Close Search",
onClick = { isSearchOpen = false }
onClick = {
packageListViewModel.closeSearch()
}
){
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search")
}
Spacer(Modifier.width(12.dp))
TextField(
value = searchQuery,
onValueChange = {str -> searchQuery = str},
value = uiState.searchQuery,
onValueChange = {
packageListViewModel.search(it)
},
Modifier
.weight(1.0f)
.focusRequester(searchBar),
@@ -248,10 +253,12 @@ fun HomePage(
),
)
Spacer(Modifier.width(12.dp))
if (searchQuery.isNotEmpty()) {
if (uiState.searchQuery.isNotEmpty()) {
NPSIconButton(
tooltip = "Clear Search",
onClick = { searchQuery = "" }
onClick = {
packageListViewModel.clearSearch()
}
){
Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search")
}
@@ -261,10 +268,10 @@ fun HomePage(
searchBar.requestFocus()
}
}
if (!isSearchOpen) {
if (!uiState.isSearchOpen) {
NPSIconButton(
tooltip = stringResource(id = R.string.search_tooltip),
onClick = { isSearchOpen = true }
onClick = { packageListViewModel.openSearch() }
){
Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource(
id = R.string.search_tooltip
@@ -275,17 +282,29 @@ fun HomePage(
tooltip = stringResource(id = R.string.layout),
onClick = { layoutDialogOpen = true }
) {
Icon(imageVector = if (settingsPrefs.layout == ItemLayout.GRID.ordinal) Icons.Filled.GridView else Icons.Filled.ViewList, contentDescription = stringResource(
Icon(
imageVector = if (settingsPrefs.layout == ItemLayout.GRID.ordinal) Icons.Filled.GridView else Icons.Filled.ViewList,
contentDescription = stringResource(
id = R.string.layout
))
)
)
}
NPSIconButton(
tooltip = "Download queue",
onClick = { /*TODO*/ }
) {
Icon(imageVector = Icons.Filled.Downloading, contentDescription = "Download queue")
}
NPSIconButton(
tooltip = stringResource(id = R.string.title_activity_settings),
onClick = { navigateToSettings() }
){
Icon(imageVector = Icons.Filled.Settings, contentDescription = stringResource(
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = stringResource(
id = R.string.title_activity_settings
))
)
)
}
}
)
@@ -306,7 +325,8 @@ fun HomePage(
PackageList(
navigateToDetailsScreen = navigateToDetailsScreen,
type = selectedBarItem,
settingsPrefs = settingsPrefs
settingsPrefs = settingsPrefs,
viewModel = packageListViewModel,
)
}
}
@@ -321,6 +341,18 @@ fun HomePage(
}
}
@Composable
fun NoResults() {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(text = "No results found", color = MaterialTheme.colorScheme.outline)
}
}
@Composable
fun NavBar(selectedTab: ConsoleType, onTabSelected: (ConsoleType) -> Unit) {
NavigationBar {
@@ -411,9 +443,7 @@ fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: ()
fun PackageList(
type: ConsoleType = ConsoleType.PSVITA,
navigateToDetailsScreen: (item: PackageItem) -> Unit,
viewModel: PackageListViewModel = viewModel(
factory = PackageListViewModel.Factory
),
viewModel: PackageListViewModel,
settingsPrefs: SettingsPreferences,
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
@@ -447,7 +477,7 @@ fun PackageList(
CircularProgressIndicator()
}
} else {
if (packages.isEmpty()) {
if (packages.isEmpty() && uiState.searchQuery.isEmpty()) {
Box(
modifier = Modifier
.fillMaxSize()
@@ -461,6 +491,8 @@ fun PackageList(
color = MaterialTheme.colorScheme.outline
)
}
} else if (packages.isEmpty() && uiState.searchQuery.isNotEmpty()) {
NoResults()
} else {
if (settingsPrefs.layout == ItemLayout.LIST.ordinal) {
LazyColumn {
@@ -8,6 +8,12 @@ import androidx.datastore.preferences.preferencesDataStore
import com.illusionware.npsbrowser.data.OnboardingPreferencesRepository
import com.illusionware.npsbrowser.data.SettingsPreferencesRepository
@OptIn(ExperimentalUnsignedTypes::class)
val HMAC_KEY = ubyteArrayOf(
0xE5U, 0xE2U, 0x78U, 0xAAU, 0x1EU, 0xE3U, 0x40U, 0x82U, 0xA0U, 0x88U, 0x27U, 0x9CU, 0x83U, 0xF9U, 0xBBU, 0xC8U,
0x06U, 0x82U, 0x1CU, 0x52U, 0xF2U, 0xABU, 0x5DU, 0x2BU, 0x4AU, 0xBDU, 0x99U, 0x54U, 0x50U, 0x35U, 0x51U, 0x14U
)
private const val PREFERENCES_NAME = "preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = PREFERENCES_NAME
@@ -40,7 +40,6 @@ data class SettingsPreferences(
val ps3Dlc: String,
val pspDlc: String,
val psvThemes: String,
val hmacKey: String,
val downloadDir: String,
val unpackDir: String,
val unpackInDownload: Boolean,
@@ -64,7 +63,6 @@ class SettingsPreferencesRepository(
private val PS3_DLC = stringPreferencesKey("ps3_dlc")
private val PSP_DLC = stringPreferencesKey("psp_dlc")
private val PSV_THEMES = stringPreferencesKey("psv_themes")
private val HMAC_KEY = stringPreferencesKey("hmac_key")
private val DOWNLOAD_DIR = stringPreferencesKey("download_dir")
private val UNPACK_DIR = stringPreferencesKey("unpack_dir")
private val UNPACK_IN_DOWNLOAD = booleanPreferencesKey("unpack_in_download")
@@ -108,12 +106,6 @@ class SettingsPreferencesRepository(
}
}
suspend fun setHMACKey(key: String) {
dataStore.edit { prefs ->
prefs[HMAC_KEY] = key
}
}
suspend fun setDownloadDir(dir: String) {
dataStore.edit { prefs ->
prefs[DOWNLOAD_DIR] = dir
@@ -207,7 +199,6 @@ class SettingsPreferencesRepository(
prefs[PS3_DLC] ?: "",
prefs[PSP_DLC] ?: "",
prefs[PSV_THEMES] ?: "",
prefs[HMAC_KEY] ?: "",
prefs[DOWNLOAD_DIR] ?: defaultDownloadDir,
prefs[UNPACK_DIR] ?: defaultDownloadDir,
prefs[UNPACK_IN_DOWNLOAD] ?: false,
@@ -63,7 +63,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material3.Button
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
@@ -74,20 +73,16 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.illusionware.npsbrowser.data.SettingsPreferences
import com.illusionware.npsbrowser.model.ConsoleType
import com.illusionware.npsbrowser.model.PackageItemType
import com.illusionware.npsbrowser.ui.components.NPSIconButton
import com.illusionware.npsbrowser.viewmodels.OnboardingViewModel
import com.illusionware.npsbrowser.viewmodels.SettingsViewModel
import kotlinx.coroutines.launch
@@ -626,10 +621,6 @@ fun OnBoardingSetupSettings(
onboardingViewModel: OnboardingViewModel,
settingsPrefs: SettingsPreferences,
) {
val clipboard = LocalClipboardManager.current
var hmacKey by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
val downloadDirPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
) { dirUri ->
@@ -646,12 +637,6 @@ fun OnBoardingSetupSettings(
}
}
fun setHmacKey(key: String) {
if (key.isNotEmpty()) {
settingsViewModel.setHMACKey(key)
}
}
Scaffold { padding ->
Column(
Modifier
@@ -685,37 +670,6 @@ fun OnBoardingSetupSettings(
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
ColumnGroup(title = "HMAC Key", modifier = Modifier.padding(horizontal = 16.dp)) {
Text(
text = "The HMAC Key is used to decrypt the PS Vita game updates' urls. We cannot provide this key since it's the property of Sony",
style = Typography.bodyMedium.copy(color = MaterialTheme.colorScheme.outline),
)
OutlinedTextField(
value = hmacKey,
onValueChange = {
hmacKey = it
setHmacKey(it.text)
},
label = { Text(text = "HMAC Key") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
trailingIcon = {
NPSIconButton(
tooltip = "Paste",
onClick = {
val text = clipboard.getText()?.text ?: ""
hmacKey = TextFieldValue(text, selection = TextRange(text.length))
setHmacKey(text)
}
) {
Icon(
imageVector = Icons.Filled.ContentPaste,
contentDescription = "Paste",
)
}
}
)
}
ColumnGroup(title = "Downloads", titleModifier = Modifier.padding(horizontal = 16.dp)) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
@@ -64,7 +64,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.illusionware.npsbrowser.R
import com.illusionware.npsbrowser.activities.PlaceholderColor
import com.illusionware.npsbrowser.PlaceholderColor
import com.illusionware.npsbrowser.data.ItemLayout
import com.illusionware.npsbrowser.data.SettingsPreferences
import com.illusionware.npsbrowser.data.Theme
@@ -443,7 +443,6 @@ fun BottomSheetPreview() {
"",
"",
"",
"",
false,
false,
"",
@@ -24,10 +24,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material.icons.outlined.Key
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Unarchive
import androidx.compose.material3.Icon
@@ -46,9 +46,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -115,12 +115,10 @@ fun SettingsScreen(
) {
Appearance(viewModel, prefs)
TsvFiles(viewModel, prefs)
Updates(viewModel, prefs)
Downloads(viewModel, prefs)
Pkg2Zip(viewModel, prefs)
}
}
}
}
@@ -188,7 +186,7 @@ fun TsvFiles(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
DialogSetting(
title = "Expand",
value = "PS3 DLC, PSP Games, PSP DLC, PSX Games, PSM Games",
icon = Icons.Outlined.ArrowDropDown,
icon = Icons.Outlined.ExpandMore,
singleLine = true,
onClick = { expanded = true },
)
@@ -271,63 +269,7 @@ fun TSVPicker(
onClick = {
filePicker.launch(arrayOf("text/tab-separated-values"))
}
)}
@Composable
fun Updates(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
val clipboard = LocalClipboardManager.current
var hmacOpenDialog by remember { mutableStateOf(false) }
var dialogHmacKey by remember { mutableStateOf("") }
SideEffect {
dialogHmacKey = prefs.hmacKey
}
SettingGroup(title = "Updates") {
DialogSetting(
title = "HMAC Key",
value = prefs.hmacKey.ifEmpty { "None" },
icon = Icons.Outlined.Key,
onClick = { hmacOpenDialog = true }
)
}
if (hmacOpenDialog) {
NPSAlertDialog(
onDismiss = { hmacOpenDialog = false },
title = "HMAC Key",
buttons = {
TextButton(onClick = { hmacOpenDialog = false }) {
Text(text = "Cancel")
}
TextButton(onClick = {
viewModel.setHMACKey(dialogHmacKey)
hmacOpenDialog = false
}) {
Text(text = "OK")
}
}
) {
OutlinedTextField(
value = dialogHmacKey,
onValueChange = { dialogHmacKey = it },
label = { Text(text = "HMAC Key") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
trailingIcon = {
NPSIconButton(
tooltip = "Paste",
onClick = { dialogHmacKey = clipboard.getText()?.text ?: "" }
) {
Icon(
imageVector = Icons.Filled.ContentPaste,
contentDescription = "Paste",
)
}
}
)
}
}
}
@Composable
@@ -575,7 +517,19 @@ fun ToggleSetting(title: String, icon: ImageVector? = null, checked: Boolean, on
Box(modifier = Modifier.width(56.dp))
}
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f))
Switch(checked = checked, onCheckedChange = null)
Switch(
checked = checked,
onCheckedChange = null,
thumbContent = if (checked) {
{
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
modifier = Modifier.size(16.dp)
)
}
} else null
)
}
}
}
@@ -19,6 +19,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import okhttp3.internal.toLongOrDefault
import java.io.InputStream
@@ -27,10 +28,13 @@ data class PackageListUiState(
val packages: ArrayList<PackageItem> = ArrayList(),
val error: String = "",
val isLoading: Boolean = true,
val isSearchOpen: Boolean = false,
val searchQuery: String = "",
)
class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesRepository): ViewModel() {
private val _uiState = MutableStateFlow(PackageListUiState())
private val immutablePackages = ArrayList<PackageItem>()
val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow()
private val tsvReader = csvReader {
@@ -52,6 +56,7 @@ class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesReposit
list.sortBy { it.name }
_uiState.emit(PackageListUiState(isLoading = false, packages = list))
immutablePackages.addAll(list)
settingsRepo.tsvFiles().collect { tsv ->
_uiState.emit(PackageListUiState())
@@ -70,10 +75,45 @@ class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesReposit
list.sortBy { it.name }
_uiState.emit(PackageListUiState(isLoading = false, packages = list))
immutablePackages.addAll(list)
}
}
}
fun search(query: String) {
_uiState.update {
it.copy(
packages = immutablePackages.filter { item ->
item.name.contains(query, true)
} as ArrayList<PackageItem>,
searchQuery = query
)
}
}
fun openSearch() {
_uiState.update {
it.copy(isSearchOpen = true)
}
}
fun closeSearch() {
_uiState.update {
it.copy(
packages = immutablePackages,
searchQuery = "",
isSearchOpen = false
)
}
}
fun clearSearch() {
_uiState.update {
it.copy(packages = immutablePackages, searchQuery = "")
}
}
private fun parsePackageFromTSV(
it: Triple<String, ConsoleType, PackageItemType>, app: NPSApp
): ArrayList<PackageItem> {
@@ -38,7 +38,6 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
"",
"",
"",
"",
false,
false,
"",
@@ -85,12 +84,6 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
}
}
fun setHMACKey(hmacKey: String) {
viewModelScope.launch(Dispatchers.IO) {
settingsPrefsRepo.setHMACKey(hmacKey)
}
}
fun setDownloadDir(downloadDir: String) {
viewModelScope.launch(Dispatchers.IO) {
settingsPrefsRepo.setDownloadDir(downloadDir)