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:theme="@style/NPSTheme"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity <activity
android:name=".activities.MainActivity" android:name=".MainActivity"
android:configChanges="screenSize|screenLayout|keyboardHidden|orientation" android:configChanges="screenSize|screenLayout|keyboardHidden|orientation"
android:exported="true" android:exported="true"
android:theme="@style/NPSTheme.Splash" android:theme="@style/NPSTheme.Splash"
@@ -1,4 +1,4 @@
package com.illusionware.npsbrowser.activities package com.illusionware.npsbrowser
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity 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.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close 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.GridView
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
@@ -84,8 +85,6 @@ import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.google.accompanist.systemuicontroller.rememberSystemUiController 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.ItemLayout
import com.illusionware.npsbrowser.data.SettingsPreferences import com.illusionware.npsbrowser.data.SettingsPreferences
import com.illusionware.npsbrowser.model.ConsoleType import com.illusionware.npsbrowser.model.ConsoleType
@@ -189,15 +188,17 @@ fun HomePage(
navigateToDetailsScreen: (item: PackageItem) -> Unit, navigateToDetailsScreen: (item: PackageItem) -> Unit,
settingsPrefs: SettingsPreferences, settingsPrefs: SettingsPreferences,
settingsViewModel: SettingsViewModel, settingsViewModel: SettingsViewModel,
packageListViewModel: PackageListViewModel = viewModel(
factory = PackageListViewModel.Factory
),
) { ) {
var isSearchOpen by rememberSaveable { mutableStateOf(false)}
var searchQuery by rememberSaveable { mutableStateOf("")}
val searchBar = remember { FocusRequester() } val searchBar = remember { FocusRequester() }
var layoutDialogOpen by remember { mutableStateOf(false) } var layoutDialogOpen by remember { mutableStateOf(false) }
val context = LocalContext.current val context = LocalContext.current
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val navbarScrimColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp) val navbarScrimColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
val uiState = packageListViewModel.uiState.collectAsStateWithLifecycle().value
val tsvs = settingsViewModel.availableTsvs() val tsvs = settingsViewModel.availableTsvs()
var selectedBarItem by rememberSaveable { mutableStateOf(ConsoleType.PSVITA) } var selectedBarItem by rememberSaveable { mutableStateOf(ConsoleType.PSVITA) }
@@ -215,22 +216,26 @@ fun HomePage(
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
if (!isSearchOpen) { if (!uiState.isSearchOpen) {
Text(text = stringResource(id = R.string.app_name)) Text(text = stringResource(id = R.string.app_name))
} }
}, },
actions = { actions = {
if (isSearchOpen) { if (uiState.isSearchOpen) {
NPSIconButton( NPSIconButton(
tooltip = "Close Search", tooltip = "Close Search",
onClick = { isSearchOpen = false } onClick = {
packageListViewModel.closeSearch()
}
){ ){
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search") Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "Close Search")
} }
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
TextField( TextField(
value = searchQuery, value = uiState.searchQuery,
onValueChange = {str -> searchQuery = str}, onValueChange = {
packageListViewModel.search(it)
},
Modifier Modifier
.weight(1.0f) .weight(1.0f)
.focusRequester(searchBar), .focusRequester(searchBar),
@@ -248,10 +253,12 @@ fun HomePage(
), ),
) )
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
if (searchQuery.isNotEmpty()) { if (uiState.searchQuery.isNotEmpty()) {
NPSIconButton( NPSIconButton(
tooltip = "Clear Search", tooltip = "Clear Search",
onClick = { searchQuery = "" } onClick = {
packageListViewModel.clearSearch()
}
){ ){
Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search") Icon(imageVector = Icons.Filled.Close, contentDescription = "Clear Search")
} }
@@ -261,10 +268,10 @@ fun HomePage(
searchBar.requestFocus() searchBar.requestFocus()
} }
} }
if (!isSearchOpen) { if (!uiState.isSearchOpen) {
NPSIconButton( NPSIconButton(
tooltip = stringResource(id = R.string.search_tooltip), tooltip = stringResource(id = R.string.search_tooltip),
onClick = { isSearchOpen = true } onClick = { packageListViewModel.openSearch() }
){ ){
Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource( Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource(
id = R.string.search_tooltip id = R.string.search_tooltip
@@ -275,17 +282,29 @@ fun HomePage(
tooltip = stringResource(id = R.string.layout), tooltip = stringResource(id = R.string.layout),
onClick = { layoutDialogOpen = true } onClick = { layoutDialogOpen = true }
) { ) {
Icon(imageVector = if (settingsPrefs.layout == ItemLayout.GRID.ordinal) Icons.Filled.GridView else Icons.Filled.ViewList, contentDescription = stringResource( Icon(
id = R.string.layout 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( NPSIconButton(
tooltip = stringResource(id = R.string.title_activity_settings), tooltip = stringResource(id = R.string.title_activity_settings),
onClick = { navigateToSettings() } onClick = { navigateToSettings() }
){ ){
Icon(imageVector = Icons.Filled.Settings, contentDescription = stringResource( Icon(
id = R.string.title_activity_settings imageVector = Icons.Filled.Settings,
)) contentDescription = stringResource(
id = R.string.title_activity_settings
)
)
} }
} }
) )
@@ -306,7 +325,8 @@ fun HomePage(
PackageList( PackageList(
navigateToDetailsScreen = navigateToDetailsScreen, navigateToDetailsScreen = navigateToDetailsScreen,
type = selectedBarItem, 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 @Composable
fun NavBar(selectedTab: ConsoleType, onTabSelected: (ConsoleType) -> Unit) { fun NavBar(selectedTab: ConsoleType, onTabSelected: (ConsoleType) -> Unit) {
NavigationBar { NavigationBar {
@@ -411,9 +443,7 @@ fun NoTsvs(padding: PaddingValues = PaddingValues(0.dp), navigateToSettings: ()
fun PackageList( fun PackageList(
type: ConsoleType = ConsoleType.PSVITA, type: ConsoleType = ConsoleType.PSVITA,
navigateToDetailsScreen: (item: PackageItem) -> Unit, navigateToDetailsScreen: (item: PackageItem) -> Unit,
viewModel: PackageListViewModel = viewModel( viewModel: PackageListViewModel,
factory = PackageListViewModel.Factory
),
settingsPrefs: SettingsPreferences, settingsPrefs: SettingsPreferences,
) { ) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
@@ -447,7 +477,7 @@ fun PackageList(
CircularProgressIndicator() CircularProgressIndicator()
} }
} else { } else {
if (packages.isEmpty()) { if (packages.isEmpty() && uiState.searchQuery.isEmpty()) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -461,6 +491,8 @@ fun PackageList(
color = MaterialTheme.colorScheme.outline color = MaterialTheme.colorScheme.outline
) )
} }
} else if (packages.isEmpty() && uiState.searchQuery.isNotEmpty()) {
NoResults()
} else { } else {
if (settingsPrefs.layout == ItemLayout.LIST.ordinal) { if (settingsPrefs.layout == ItemLayout.LIST.ordinal) {
LazyColumn { LazyColumn {
@@ -8,6 +8,12 @@ import androidx.datastore.preferences.preferencesDataStore
import com.illusionware.npsbrowser.data.OnboardingPreferencesRepository import com.illusionware.npsbrowser.data.OnboardingPreferencesRepository
import com.illusionware.npsbrowser.data.SettingsPreferencesRepository 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 const val PREFERENCES_NAME = "preferences"
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = PREFERENCES_NAME name = PREFERENCES_NAME
@@ -40,7 +40,6 @@ data class SettingsPreferences(
val ps3Dlc: String, val ps3Dlc: String,
val pspDlc: String, val pspDlc: String,
val psvThemes: String, val psvThemes: String,
val hmacKey: String,
val downloadDir: String, val downloadDir: String,
val unpackDir: String, val unpackDir: String,
val unpackInDownload: Boolean, val unpackInDownload: Boolean,
@@ -64,7 +63,6 @@ class SettingsPreferencesRepository(
private val PS3_DLC = stringPreferencesKey("ps3_dlc") private val PS3_DLC = stringPreferencesKey("ps3_dlc")
private val PSP_DLC = stringPreferencesKey("psp_dlc") private val PSP_DLC = stringPreferencesKey("psp_dlc")
private val PSV_THEMES = stringPreferencesKey("psv_themes") private val PSV_THEMES = stringPreferencesKey("psv_themes")
private val HMAC_KEY = stringPreferencesKey("hmac_key")
private val DOWNLOAD_DIR = stringPreferencesKey("download_dir") private val DOWNLOAD_DIR = stringPreferencesKey("download_dir")
private val UNPACK_DIR = stringPreferencesKey("unpack_dir") private val UNPACK_DIR = stringPreferencesKey("unpack_dir")
private val UNPACK_IN_DOWNLOAD = booleanPreferencesKey("unpack_in_download") 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) { suspend fun setDownloadDir(dir: String) {
dataStore.edit { prefs -> dataStore.edit { prefs ->
prefs[DOWNLOAD_DIR] = dir prefs[DOWNLOAD_DIR] = dir
@@ -207,7 +199,6 @@ class SettingsPreferencesRepository(
prefs[PS3_DLC] ?: "", prefs[PS3_DLC] ?: "",
prefs[PSP_DLC] ?: "", prefs[PSP_DLC] ?: "",
prefs[PSV_THEMES] ?: "", prefs[PSV_THEMES] ?: "",
prefs[HMAC_KEY] ?: "",
prefs[DOWNLOAD_DIR] ?: defaultDownloadDir, prefs[DOWNLOAD_DIR] ?: defaultDownloadDir,
prefs[UNPACK_DIR] ?: defaultDownloadDir, prefs[UNPACK_DIR] ?: defaultDownloadDir,
prefs[UNPACK_IN_DOWNLOAD] ?: false, prefs[UNPACK_IN_DOWNLOAD] ?: false,
@@ -63,7 +63,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon 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.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layoutId 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.TextStyle
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.illusionware.npsbrowser.data.SettingsPreferences import com.illusionware.npsbrowser.data.SettingsPreferences
import com.illusionware.npsbrowser.model.ConsoleType import com.illusionware.npsbrowser.model.ConsoleType
import com.illusionware.npsbrowser.model.PackageItemType import com.illusionware.npsbrowser.model.PackageItemType
import com.illusionware.npsbrowser.ui.components.NPSIconButton
import com.illusionware.npsbrowser.viewmodels.OnboardingViewModel import com.illusionware.npsbrowser.viewmodels.OnboardingViewModel
import com.illusionware.npsbrowser.viewmodels.SettingsViewModel import com.illusionware.npsbrowser.viewmodels.SettingsViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -626,10 +621,6 @@ fun OnBoardingSetupSettings(
onboardingViewModel: OnboardingViewModel, onboardingViewModel: OnboardingViewModel,
settingsPrefs: SettingsPreferences, settingsPrefs: SettingsPreferences,
) { ) {
val clipboard = LocalClipboardManager.current
var hmacKey by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
val downloadDirPicker = rememberLauncherForActivityResult( val downloadDirPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(), contract = ActivityResultContracts.OpenDocumentTree(),
) { dirUri -> ) { dirUri ->
@@ -646,12 +637,6 @@ fun OnBoardingSetupSettings(
} }
} }
fun setHmacKey(key: String) {
if (key.isNotEmpty()) {
settingsViewModel.setHMACKey(key)
}
}
Scaffold { padding -> Scaffold { padding ->
Column( Column(
Modifier Modifier
@@ -685,37 +670,6 @@ fun OnBoardingSetupSettings(
.padding(vertical = 16.dp), .padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(24.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)) { ColumnGroup(title = "Downloads", titleModifier = Modifier.padding(horizontal = 16.dp)) {
Text( Text(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
@@ -64,7 +64,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.illusionware.npsbrowser.R 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.ItemLayout
import com.illusionware.npsbrowser.data.SettingsPreferences import com.illusionware.npsbrowser.data.SettingsPreferences
import com.illusionware.npsbrowser.data.Theme import com.illusionware.npsbrowser.data.Theme
@@ -443,7 +443,6 @@ fun BottomSheetPreview() {
"", "",
"", "",
"", "",
"",
false, false,
false, false,
"", "",
@@ -24,10 +24,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack 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.ArrowDropDown
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.FileDownload 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.Palette
import androidx.compose.material.icons.outlined.Unarchive import androidx.compose.material.icons.outlined.Unarchive
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -46,9 +46,9 @@ import androidx.compose.runtime.setValue
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.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -115,12 +115,10 @@ fun SettingsScreen(
) { ) {
Appearance(viewModel, prefs) Appearance(viewModel, prefs)
TsvFiles(viewModel, prefs) TsvFiles(viewModel, prefs)
Updates(viewModel, prefs)
Downloads(viewModel, prefs) Downloads(viewModel, prefs)
Pkg2Zip(viewModel, prefs) Pkg2Zip(viewModel, prefs)
} }
} }
} }
} }
@@ -188,7 +186,7 @@ fun TsvFiles(viewModel: SettingsViewModel, prefs: SettingsPreferences) {
DialogSetting( DialogSetting(
title = "Expand", title = "Expand",
value = "PS3 DLC, PSP Games, PSP DLC, PSX Games, PSM Games", value = "PS3 DLC, PSP Games, PSP DLC, PSX Games, PSM Games",
icon = Icons.Outlined.ArrowDropDown, icon = Icons.Outlined.ExpandMore,
singleLine = true, singleLine = true,
onClick = { expanded = true }, onClick = { expanded = true },
) )
@@ -271,63 +269,7 @@ fun TSVPicker(
onClick = { onClick = {
filePicker.launch(arrayOf("text/tab-separated-values")) 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 @Composable
@@ -575,7 +517,19 @@ fun ToggleSetting(title: String, icon: ImageVector? = null, checked: Boolean, on
Box(modifier = Modifier.width(56.dp)) Box(modifier = Modifier.width(56.dp))
} }
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f)) 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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.internal.toLongOrDefault import okhttp3.internal.toLongOrDefault
import java.io.InputStream import java.io.InputStream
@@ -27,10 +28,13 @@ data class PackageListUiState(
val packages: ArrayList<PackageItem> = ArrayList(), val packages: ArrayList<PackageItem> = ArrayList(),
val error: String = "", val error: String = "",
val isLoading: Boolean = true, val isLoading: Boolean = true,
val isSearchOpen: Boolean = false,
val searchQuery: String = "",
) )
class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesRepository): ViewModel() { class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesRepository): ViewModel() {
private val _uiState = MutableStateFlow(PackageListUiState()) private val _uiState = MutableStateFlow(PackageListUiState())
private val immutablePackages = ArrayList<PackageItem>()
val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow() val uiState: StateFlow<PackageListUiState> = _uiState.asStateFlow()
private val tsvReader = csvReader { private val tsvReader = csvReader {
@@ -52,6 +56,7 @@ class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesReposit
list.sortBy { it.name } list.sortBy { it.name }
_uiState.emit(PackageListUiState(isLoading = false, packages = list)) _uiState.emit(PackageListUiState(isLoading = false, packages = list))
immutablePackages.addAll(list)
settingsRepo.tsvFiles().collect { tsv -> settingsRepo.tsvFiles().collect { tsv ->
_uiState.emit(PackageListUiState()) _uiState.emit(PackageListUiState())
@@ -70,10 +75,45 @@ class PackageListViewModel(app: NPSApp, settingsRepo: SettingsPreferencesReposit
list.sortBy { it.name } list.sortBy { it.name }
_uiState.emit(PackageListUiState(isLoading = false, packages = list)) _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( private fun parsePackageFromTSV(
it: Triple<String, ConsoleType, PackageItemType>, app: NPSApp it: Triple<String, ConsoleType, PackageItemType>, app: NPSApp
): ArrayList<PackageItem> { ): ArrayList<PackageItem> {
@@ -38,7 +38,6 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
"", "",
"", "",
"", "",
"",
false, false,
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) { fun setDownloadDir(downloadDir: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
settingsPrefsRepo.setDownloadDir(downloadDir) settingsPrefsRepo.setDownloadDir(downloadDir)