preference saving

This commit is contained in:
2023-06-28 04:49:41 +03:00
parent 5c7c1d9036
commit 4d6fb0419e
3 changed files with 615 additions and 126 deletions
@@ -1,18 +1,51 @@
package com.illusionware.npsbrowser
import android.content.Context
import android.net.Uri
import android.os.Environment
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.map
enum class ConsoleType {
PSVITA,
PS3,
PSP,
PSX,
PSM,
}
enum class DataType {
GAMES,
DLC,
THEMES,
}
class Preferences(private val context: Context) {
companion object {
private val Context.dataStore by preferencesDataStore(name = "settings")
private val Context.dataStore by preferencesDataStore("settings")
private val APP_THEME = intPreferencesKey("app_theme")
private val DATA_LAYOUT = intPreferencesKey("layout")
private val SEEN_ONBOARDING = booleanPreferencesKey("seen_onboarding")
private val PSV_GAMES = stringPreferencesKey("psv_games")
private val PS3_GAMES = stringPreferencesKey("ps3_games")
private val PSP_GAMES = stringPreferencesKey("psp_games")
private val PSX_GAMES = stringPreferencesKey("psx_games")
private val PSM_GAMES = stringPreferencesKey("psm_games")
private val PSV_DLC = stringPreferencesKey("psv_dlc")
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")
private val DELETE_AFTER_UNPACK = booleanPreferencesKey("delete_after_unpack")
private val PKG2ZIP_PARAMS = stringPreferencesKey("pkg2zip_params")
private val PKG2ZIP_AUTO_DECRYPT = booleanPreferencesKey("pkg2zip_auto_decrypt")
}
val theme = context.dataStore.data.map { prefs ->
@@ -27,6 +60,70 @@ class Preferences(private val context: Context) {
prefs[SEEN_ONBOARDING] ?: false
}
val psvGames = context.dataStore.data.map{ prefs ->
prefs[PSV_GAMES] ?: ""
}
val ps3Games = context.dataStore.data.map{ prefs ->
prefs[PS3_GAMES] ?: ""
}
val pspGames = context.dataStore.data.map{ prefs ->
prefs[PSP_GAMES] ?: ""
}
val psxGames = context.dataStore.data.map{ prefs ->
prefs[PSX_GAMES] ?: ""
}
val psmGames = context.dataStore.data.map{ prefs ->
prefs[PSM_GAMES] ?: ""
}
val psvDLC = context.dataStore.data.map{ prefs ->
prefs[PSV_DLC] ?: ""
}
val ps3DLC = context.dataStore.data.map{ prefs ->
prefs[PS3_DLC] ?: ""
}
val pspDLC = context.dataStore.data.map{ prefs ->
prefs[PSP_DLC] ?: ""
}
val psvThemes = context.dataStore.data.map{ prefs ->
prefs[PSV_THEMES] ?: ""
}
val hmacKey = context.dataStore.data.map{ prefs ->
prefs[HMAC_KEY] ?: ""
}
val downloadDir = context.dataStore.data.map{ prefs ->
prefs[DOWNLOAD_DIR] ?: Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.path ?: ""
}
val unpackDir = context.dataStore.data.map{ prefs ->
prefs[UNPACK_DIR] ?: Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.path ?: ""
}
val unpackInDownload = context.dataStore.data.map{ prefs ->
prefs[UNPACK_IN_DOWNLOAD] ?: false
}
val deleteAfterUnpack = context.dataStore.data.map{ prefs ->
prefs[DELETE_AFTER_UNPACK] ?: false
}
val pkg2zipParams = context.dataStore.data.map{ prefs ->
prefs[PKG2ZIP_PARAMS] ?: "-x {pkgFile} \"{zRifKey}\""
}
val pkg2zipAutoDecrypt = context.dataStore.data.map{ prefs ->
prefs[PKG2ZIP_AUTO_DECRYPT] ?: true
}
suspend fun changeTheme(theme: Int) {
context.dataStore.edit { prefs ->
prefs[APP_THEME] = theme
@@ -44,4 +141,87 @@ class Preferences(private val context: Context) {
prefs[SEEN_ONBOARDING] = true
}
}
suspend fun setTSVFile(console: ConsoleType, contentType: DataType, file: Uri) {
context.dataStore.edit { prefs ->
when (console) {
ConsoleType.PSVITA -> {
when (contentType) {
DataType.GAMES -> prefs[PSV_GAMES] = file.toString()
DataType.DLC -> prefs[PSV_DLC] = file.toString()
DataType.THEMES -> prefs[PSV_THEMES] = file.toString()
}
}
ConsoleType.PS3 -> {
when (contentType) {
DataType.GAMES -> prefs[PS3_GAMES] = file.toString()
DataType.DLC -> prefs[PS3_DLC] = file.toString()
else -> {}
}
}
ConsoleType.PSP -> {
when (contentType) {
DataType.GAMES -> prefs[PSP_GAMES] = file.toString()
DataType.DLC -> prefs[PSP_DLC] = file.toString()
else -> {}
}
}
ConsoleType.PSX -> {
when (contentType) {
DataType.GAMES -> prefs[PSX_GAMES] = file.toString()
else -> {}
}
}
ConsoleType.PSM -> {
when (contentType) {
DataType.GAMES -> prefs[PSM_GAMES] = file.toString()
else -> {}
}
}
}
}
}
suspend fun setHMACKey(key: String) {
context.dataStore.edit { prefs ->
prefs[HMAC_KEY] = key
}
}
suspend fun setDownloadDir(dir: String) {
context.dataStore.edit { prefs ->
prefs[DOWNLOAD_DIR] = dir
}
}
suspend fun setUnpackDir(dir: String) {
context.dataStore.edit { prefs ->
prefs[UNPACK_DIR] = dir
}
}
suspend fun setUnpackInDownload(unpackInDownload: Boolean) {
context.dataStore.edit { prefs ->
prefs[UNPACK_IN_DOWNLOAD] = unpackInDownload
}
}
suspend fun setDeleteAfterUnpack(deleteAfterUnpack: Boolean) {
context.dataStore.edit { prefs ->
prefs[DELETE_AFTER_UNPACK] = deleteAfterUnpack
}
}
suspend fun setPkg2zipParams(params: String) {
context.dataStore.edit { prefs ->
prefs[PKG2ZIP_PARAMS] = params
}
}
suspend fun setPkg2zipAutoDecrypt(autoDecrypt: Boolean) {
context.dataStore.edit { prefs ->
prefs[PKG2ZIP_AUTO_DECRYPT] = autoDecrypt
}
}
}
@@ -3,7 +3,7 @@ package com.illusionware.npsbrowser.ui.pages
import android.annotation.SuppressLint
import android.app.Activity
import android.content.pm.ActivityInfo
import android.util.Log
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
@@ -68,15 +68,21 @@ import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
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.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.sp
import com.illusionware.npsbrowser.ConsoleType
import com.illusionware.npsbrowser.DataType
import com.illusionware.npsbrowser.Preferences
import com.illusionware.npsbrowser.ui.components.NPSIconButton
import kotlinx.coroutines.launch
@@ -229,6 +235,9 @@ fun OnBoardingSetupTSVs(
incrementPage: () -> Unit = {},
skipToEnd: () -> Unit = {}
) {
val prefs = Preferences(LocalContext.current)
val scope = rememberCoroutineScope()
Scaffold { padding ->
Column(
Modifier
@@ -277,25 +286,52 @@ fun OnBoardingSetupTSVs(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
TSVGroup(title = "PS Vita") {
TSVButton("Games")
TSVButton("Themes")
TSVButton("DLC")
TSVButton(
"Games",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSVITA, DataType.GAMES, it) }}
)
TSVButton(
"Themes",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSVITA, DataType.THEMES, it) }}
)
TSVButton(
"DLC",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSVITA, DataType.DLC, it) }}
)
}
TSVGroup(title = "PSP") {
TSVButton("Games")
TSVButton("DLC")
TSVButton(
"Games",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSP, DataType.GAMES, it) }}
)
TSVButton(
"DLC",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSP, DataType.DLC, it) }}
)
Box(modifier = Modifier.width(110.dp))
}
TSVGroup(title = "PS3") {
TSVButton("Games")
TSVButton("DLC")
TSVButton(
"Games",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PS3, DataType.GAMES, it) }}
)
TSVButton(
"DLC",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PS3, DataType.DLC, it) }}
)
Box(modifier = Modifier.width(110.dp))
}
TSVGroup(title = "PSX") {
TSVButton("Games")
TSVButton(
"Games",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSX, DataType.GAMES, it) }}
)
}
TSVGroup(title = "PSM") {
TSVButton("Games")
TSVButton(
"Games",
onFinish = { scope.launch { prefs.setTSVFile(ConsoleType.PSM, DataType.GAMES, it) }}
)
}
}
}
@@ -360,15 +396,14 @@ fun TSVGroup(title: String, content: @Composable RowScope.() -> Unit) {
}
@Composable
fun TSVButton(title: String) {
fun TSVButton(title: String, onFinish: (Uri) -> Unit) {
var hasSelected by rememberSaveable { mutableStateOf(false) }
val filePicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
) { fileUri ->
if (fileUri != null) {
// TODO: store file URI in preferences
Log.d("TSVItem", "fileUri: $fileUri")
onFinish(fileUri)
hasSelected = true
}
}
@@ -442,8 +477,44 @@ fun OnBoardingSetupSettings(
navigateToHome: () -> Unit = {},
) {
val clipboard = LocalClipboardManager.current
var hmacKey by rememberSaveable { mutableStateOf("") }
var pkg2zipArgs by rememberSaveable { mutableStateOf("") }
val prefs = Preferences(LocalContext.current)
val scope = rememberCoroutineScope()
var hmacKey by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
var pkg2zipArgs = prefs.pkg2zipParams.collectAsState(initial = "").value
val downloadDir = prefs.downloadDir.collectAsState(initial = "").value
val unpackDir = prefs.unpackDir.collectAsState(initial = "").value
val unpackInDownload = prefs.unpackInDownload.collectAsState(initial = false).value
val deleteAfterUnpack = prefs.deleteAfterUnpack.collectAsState(initial = false).value
val autoDecrypt = prefs.pkg2zipAutoDecrypt.collectAsState(initial = true).value
val downloadDirPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
) { dirUri ->
if (dirUri != null) {
scope.launch {
prefs.setDownloadDir(dirUri.toString())
}
}
}
val unpackDirPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
) { dirUri ->
if (dirUri != null) {
scope.launch {
prefs.setUnpackDir(dirUri.toString())
}
}
}
fun setHmacKey(key: String) {
if (key.isNotEmpty()) {
scope.launch {
prefs.setHMACKey(key)
}
}
}
Scaffold { padding ->
Column(
@@ -485,14 +556,21 @@ fun OnBoardingSetupSettings(
)
OutlinedTextField(
value = hmacKey,
onValueChange = { hmacKey = it },
onValueChange = {
hmacKey = it
setHmacKey(it.text)
},
label = { Text(text = "HMAC Key") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
trailingIcon = {
NPSIconButton(
tooltip = "Paste",
onClick = { hmacKey = clipboard.getText()?.text ?: "" }
onClick = {
val text = clipboard.getText()?.text ?: ""
hmacKey = TextFieldValue(text, selection = TextRange(text.length))
setHmacKey(text)
}
) {
Icon(
imageVector = Icons.Filled.ContentPaste,
@@ -508,36 +586,54 @@ fun OnBoardingSetupSettings(
text = "Choose your desired directory to download and unpack packages",
style = Typography.bodyMedium.copy(color = MaterialTheme.colorScheme.outline),
)
Toggleable(
title = "Unpack in download directory",
)
Toggleable(
title = "Delete package after unpacking",
)
Box(
Modifier.fillMaxWidth().clickable { /*TODO*/ }
) {
Column(Modifier.padding(horizontal = 16.dp)) {
Text(
text = "Download Directory",
)
Text(
text = "sdcard/Downloads",
color = MaterialTheme.colorScheme.outline,
)
Column {
Toggleable(
title = "Unpack in download directory",
checked = unpackInDownload,
onCheckedChange = {
scope.launch {
prefs.setUnpackInDownload(it)
}
},
)
Toggleable(
title = "Delete package after unpacking",
checked = deleteAfterUnpack,
onCheckedChange = {
scope.launch {
prefs.setDeleteAfterUnpack(it)
}
},
)
Box(
Modifier
.fillMaxWidth()
.clickable { downloadDirPicker.launch(Uri.EMPTY) }
) {
Column(Modifier.padding(16.dp)) {
Text(
text = "Download Directory",
)
Text(
text = Uri.decode(downloadDir).ifEmpty { "None" },
color = MaterialTheme.colorScheme.outline,
)
}
}
}
Box(
Modifier.fillMaxWidth().clickable { /*TODO*/ }
) {
Column(Modifier.padding(horizontal = 16.dp)) {
Text(
text = "Unpack Directory",
)
Text(
text = "sdcard/Unpack",
color = MaterialTheme.colorScheme.outline,
)
Box(
Modifier
.fillMaxWidth()
.clickable { unpackDirPicker.launch(Uri.EMPTY) }
) {
Column(Modifier.padding(16.dp)) {
Text(
text = "Unpack Directory",
)
Text(
text = Uri.decode(unpackDir).ifEmpty { "None" },
color = MaterialTheme.colorScheme.outline,
)
}
}
}
}
@@ -549,11 +645,24 @@ fun OnBoardingSetupSettings(
)
Toggleable(
title = "Automatically decrypt downloaded content",
checked = autoDecrypt,
onCheckedChange = {
scope.launch {
prefs.setPkg2zipAutoDecrypt(it)
}
},
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
value = pkg2zipArgs,
onValueChange = { pkg2zipArgs = it }, // TODO: validate args are legal ??
onValueChange = {
pkg2zipArgs = it
scope.launch {
prefs.setPkg2zipParams(it)
}
}, // TODO: validate args are legal ??
label = { Text(text = "Pkg2zip args") },
placeholder = { Text(text = "-x {pkgFile} \"{zRifKey}\"") },
)
@@ -576,9 +685,13 @@ fun OnBoardingSetupSettings(
}
@Composable
fun Toggleable(title: String) {
fun Toggleable(title: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
Box(
modifier = Modifier.clickable { /* TODO: */ },
modifier = Modifier
.clickable onCheckedChange@{
onCheckedChange(!checked)
}
.padding(vertical = 16.dp),
) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
@@ -586,7 +699,7 @@ fun Toggleable(title: String) {
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f))
Switch(checked = true, onCheckedChange = { /* TODO: */ })
Switch(checked = checked, onCheckedChange = null)
}
}
}
@@ -599,20 +712,25 @@ fun OnBoardingBottomButtons(
skipToEnd: () -> Unit = {},
navigateToHome: () -> Unit = {},
) {
val prefs = Preferences(LocalContext.current)
val scope = rememberCoroutineScope()
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.1f),
horizontalArrangement = Arrangement.SpaceBetween,
horizontalArrangement = if (page != 2) Arrangement.SpaceBetween else Arrangement.End,
) {
FilledTonalButton(
onClick = skipToEnd,
colors = ButtonDefaults.filledTonalButtonColors(
contentColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(horizontal = 18.dp)
) {
Text(text = "Skip")
if (page == 1) {
FilledTonalButton(
onClick = skipToEnd,
colors = ButtonDefaults.filledTonalButtonColors(
contentColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(horizontal = 18.dp)
) {
Text(text = "Skip")
}
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
if (page != 1) {
@@ -621,7 +739,15 @@ fun OnBoardingBottomButtons(
}
}
if (page == 2) {
Button(onClick = navigateToHome, contentPadding = PaddingValues(horizontal = 18.dp)) {
Button(
onClick = {
navigateToHome()
scope.launch {
prefs.finishOnboarding()
}
},
contentPadding = PaddingValues(horizontal = 18.dp)
) {
Text(text = "Finish")
}
} else {
@@ -1,5 +1,8 @@
package com.illusionware.npsbrowser.ui.pages
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -15,34 +18,36 @@ 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.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.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import com.illusionware.npsbrowser.Preferences
import com.illusionware.npsbrowser.R
import com.illusionware.npsbrowser.ui.components.NPSAlertDialog
@@ -54,6 +59,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
val Themes = hashMapOf(
0 to "Light",
1 to "Dark",
2 to "Use System Theme"
)
@Composable
@Preview
fun SettingsScreen(navigationGoBack: () -> Unit = {}, onThemeChange: (v: Int) -> Unit = {}) {
@@ -79,46 +90,18 @@ fun SettingsScreen(navigationGoBack: () -> Unit = {}, onThemeChange: (v: Int) ->
) {
Column(verticalArrangement = Arrangement.spacedBy(20.dp), modifier = Modifier.padding(vertical = 16.dp)) {
Appearance(onThemeChange)
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Header("Updates")
Spacer(modifier = Modifier.size(2.dp))
DialogSetting(title = "HMAC Key", value = "None", icon = Icons.Outlined.Key)
}
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Header("Downloads")
Spacer(modifier = Modifier.size(2.dp))
ToggleSetting(title = "Unpack in Download Directory")
DialogSetting(title = "Download Directory", value = "sdcard/Downloads", icon = Icons.Outlined.FileDownload)
DialogSetting(title = "Unpack Directory", value = "sdcard/unpack", icon = Icons.Outlined.Unarchive)
}
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Header("Pkg2zip")
Spacer(modifier = Modifier.size(2.dp))
ToggleSetting(title = "Automatically decrypt downloaded content")
DialogSetting(title = "Decryption params", value = "-x {pkgFile} \"{zRifKey}\"", icon = painterResource(
id = R.drawable.lock_open
))
}
Updates()
Downloads()
Pkg2Zip()
}
}
}
}
@Composable
fun SettingGroup(title: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Header(title)
Spacer(modifier = Modifier.size(2.dp))
content()
}
}
val Themes = hashMapOf(
0 to "Light",
1 to "Dark",
2 to "Use System Theme"
)
@Composable
fun Appearance(onThemeChange: (v: Int) -> Unit) {
@@ -143,6 +126,197 @@ fun Appearance(onThemeChange: (v: Int) -> Unit) {
}
}
@Composable
fun Updates() {
val prefs = Preferences(LocalContext.current)
val scope = rememberCoroutineScope()
val clipboard = LocalClipboardManager.current
val hmacKey = prefs.hmacKey.collectAsState(initial = "").value
var hmacOpenDialog by remember { mutableStateOf(false) }
var dialogHmacKey by remember { mutableStateOf("") }
SideEffect {
dialogHmacKey = hmacKey
}
SettingGroup(title = "Updates") {
DialogSetting(
title = "HMAC Key",
value = 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 = {
scope.launch {
prefs.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
fun Downloads() {
val prefs = Preferences(LocalContext.current)
val scope = rememberCoroutineScope()
val downloadDir = prefs.downloadDir.collectAsState(initial = "").value
val unpackDir = prefs.unpackDir.collectAsState(initial = "").value
val unpackInDownload = prefs.unpackInDownload.collectAsState(initial = false).value
val deleteAfterUnpack = prefs.deleteAfterUnpack.collectAsState(initial = false).value
val downloadDirPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
) { dirUri ->
if (dirUri != null) {
scope.launch {
prefs.setDownloadDir(dirUri.toString())
}
}
}
val unpackDirPicker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
) { dirUri ->
if (dirUri != null) {
scope.launch {
prefs.setUnpackDir(dirUri.toString())
}
}
}
SettingGroup(title = "Downloads") {
ToggleSetting(
title = "Unpack in Download Directory",
checked = unpackInDownload,
onClick = {
scope.launch {
prefs.setUnpackInDownload(it)
}
}
)
ToggleSetting(
title = "Delete package after unpacking",
checked = deleteAfterUnpack,
onClick = {
scope.launch {
prefs.setDeleteAfterUnpack(it)
}
}
)
DialogSetting(
title = "Download Directory",
value = downloadDir,
icon = Icons.Outlined.FileDownload,
onClick = {
downloadDirPicker.launch(Uri.EMPTY)
}
)
DialogSetting(
title = "Unpack Directory",
value = unpackDir,
icon = Icons.Outlined.Unarchive,
onClick = {
unpackDirPicker.launch(Uri.EMPTY)
}
)
}
}
@Composable
fun Pkg2Zip() {
val prefs = Preferences(LocalContext.current)
val scope = rememberCoroutineScope()
val autoDecrypt = prefs.pkg2zipAutoDecrypt.collectAsState(initial = true).value
val pkg2zipParams = prefs.pkg2zipParams.collectAsState(initial = "").value
var pkg2zipOpenDialog by remember { mutableStateOf(false) }
var dialogPkg2zipParams by remember { mutableStateOf("") }
SideEffect {
dialogPkg2zipParams = pkg2zipParams
}
SettingGroup(title = "Pkg2Zip") {
ToggleSetting(
title = "Automatically decrypt downloaded content",
checked = autoDecrypt,
onClick = {
scope.launch {
prefs.setPkg2zipAutoDecrypt(it)
}
}
)
DialogSetting(
title = "Decryption params",
value = pkg2zipParams.ifEmpty { "None" },
icon = painterResource(
id = R.drawable.lock_open
),
onClick = { pkg2zipOpenDialog = true }
)
}
if (pkg2zipOpenDialog) {
NPSAlertDialog(
onDismiss = { pkg2zipOpenDialog = false },
title = "PKG2ZIP Args",
buttons = {
TextButton(onClick = { pkg2zipOpenDialog = false }) {
Text(text = "Cancel")
}
TextButton(onClick = {
scope.launch {
prefs.setPkg2zipParams(dialogPkg2zipParams)
}
pkg2zipOpenDialog = false
}) {
Text(text = "Ok")
}
}
) {
OutlinedTextField(
value = dialogPkg2zipParams,
onValueChange = { dialogPkg2zipParams = it },
label = { Text(text = "PKG2ZIP Args") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
)
}
}
}
@Composable
fun ThemeDialog(onDismiss: () -> Unit, onThemeChange: (v: Int) -> Unit) {
val prefs = Preferences(LocalContext.current)
@@ -187,6 +361,15 @@ fun ThemeDialog(onDismiss: () -> Unit, onThemeChange: (v: Int) -> Unit) {
}
}
@Composable
fun SettingGroup(title: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Header(title)
Spacer(modifier = Modifier.size(2.dp))
content()
}
}
@Composable
fun Header(title: String) {
Row(Modifier.padding(start = 56.dp)) {
@@ -195,29 +378,7 @@ fun Header(title: String) {
}
@Composable
// TODO: remove the default value for onClick
fun DialogSetting(title: String, value: String, icon: Painter, onClick: () -> Unit = {}) {
Box(
Modifier
.fillMaxWidth()
.clickable { onClick() }
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp)
) {
Icon(painter = icon, contentDescription = "TODO:", modifier = Modifier.width(56.dp))
Column {
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
Text(text = value, color = ColorSecondaryLight)
}
}
}
}
@Composable
// TODO: remove the default value for onClick
fun DialogSetting(title: String, value: String, icon: ImageVector, onClick: () -> Unit = {}) {
fun DialogSetting(title: String, value: String, icon: Painter, onClick: () -> Unit) {
Box(
Modifier
.fillMaxWidth()
@@ -227,7 +388,7 @@ fun DialogSetting(title: String, value: String, icon: ImageVector, onClick: () -
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp)
) {
Icon(imageVector = icon, contentDescription = "TODO:", modifier = Modifier.width(56.dp))
Icon(painter = icon, contentDescription = null, modifier = Modifier.width(56.dp))
Column {
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
Text(text = value, color = ColorSecondaryLight)
@@ -237,9 +398,31 @@ fun DialogSetting(title: String, value: String, icon: ImageVector, onClick: () -
}
@Composable
fun ToggleSetting(title: String, icon: ImageVector? = null) {
fun DialogSetting(title: String, value: String, icon: ImageVector, onClick: () -> Unit) {
Box(
modifier = Modifier.clickable { /* TODO: */ },
Modifier
.fillMaxWidth()
.clickable { onClick() }
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 8.dp)
) {
Icon(imageVector = icon, contentDescription = null, modifier = Modifier.width(56.dp))
Column {
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
Text(text = value, color = ColorSecondaryLight)
}
}
}
}
@Composable
fun ToggleSetting(title: String, icon: ImageVector? = null, checked: Boolean, onClick: (Boolean) -> Unit) {
Box(
modifier = Modifier.clickable onClick@{
onClick(!checked)
},
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -247,12 +430,12 @@ fun ToggleSetting(title: String, icon: ImageVector? = null) {
modifier = Modifier.padding(top = 8.dp, end = 24.dp, bottom = 8.dp),
) {
if (icon != null) {
Icon(imageVector = icon, contentDescription = "TODO:", Modifier.width(56.dp))
Icon(imageVector = icon, contentDescription = null, Modifier.width(56.dp))
} else {
Box(modifier = Modifier.width(56.dp))
}
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f))
Switch(checked = true, onCheckedChange = { /* TODO: */ })
Switch(checked = checked, onCheckedChange = null)
}
}
}