add option to load colors

This commit is contained in:
Helium314 2025-02-11 23:21:34 +01:00
parent 6d9763d079
commit e105f39752
3 changed files with 95 additions and 13 deletions

View file

@ -378,6 +378,22 @@ private constructor(val themeId: Int, @JvmField val mStyleId: Int) {
return colorMap
}
fun getUnusedThemeName(initialName: String, prefs: SharedPreferences): String {
val existingNames = prefs.all.keys.mapNotNull {
when {
it.startsWith(Settings.PREF_USER_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_COLORS_PREFIX)
it.startsWith(Settings.PREF_USER_ALL_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_ALL_COLORS_PREFIX)
it.startsWith(Settings.PREF_USER_MORE_COLORS_PREFIX) -> it.substringAfter(Settings.PREF_USER_MORE_COLORS_PREFIX)
else -> null
}
}.toSortedSet()
if (initialName !in existingNames) return initialName
var i = 1
while ("$initialName$i" in existingNames)
i++
return "$initialName$i"
}
// returns false if not renamed due to invalid name or collision
fun renameUserColors(from: String, to: String, prefs: SharedPreferences): Boolean {
if (to.isBlank()) return false // don't want that

View file

@ -1,6 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
package helium314.keyboard.settings.dialogs
import android.app.Activity
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
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.Row
@ -31,16 +38,23 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import helium314.keyboard.keyboard.ColorSetting
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.decodeBase36
import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.Setting
import helium314.keyboard.settings.SettingsDestination
import helium314.keyboard.settings.keyboardNeedsReload
import helium314.keyboard.settings.screens.SaveThoseColors
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import java.util.EnumMap
// specialized variant of ListPickerDialog
@Composable
fun ColorThemePickerDialog(
onDismissRequest: () -> Unit,
@ -71,16 +85,14 @@ fun ColorThemePickerDialog(
val index = colors.indexOf(selectedColor)
if (index != -1) state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3)
}
var showLoadDialog by remember { mutableStateOf(false) }
ThreeButtonAlertDialog(
onDismissRequest = onDismissRequest,
cancelButtonText = stringResource(R.string.dialog_close),
onConfirmed = { },
confirmButtonText = null,
// neutralButtonText = stringResource(R.string.load),
onNeutral = {
// todo: launcher to select file
// when importing make sure name is not in use
},
neutralButtonText = stringResource(R.string.load),
onNeutral = { showLoadDialog = true },
title = { Text(setting.title) },
text = {
CompositionLocalProvider(
@ -173,6 +185,66 @@ fun ColorThemePickerDialog(
}
},
)
if (showLoadDialog) {
var errorDialog by remember { mutableStateOf(false) }
val loadFilePicker = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK) return@rememberLauncherForActivityResult
val uri = it.data?.data ?: return@rememberLauncherForActivityResult
ctx.getActivity()?.contentResolver?.openInputStream(uri)?.use {
errorDialog = loadColorString(it.reader().readText(), prefs)
}
}
ConfirmationDialog(
onDismissRequest = { showLoadDialog = false },
onConfirmed = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/*", "application/octet-stream", "application/json"))
.setType("*/*")
loadFilePicker.launch(intent)
},
confirmButtonText = stringResource(R.string.button_load_custom),
onNeutral = {
val cm = ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = cm.primaryClip?.takeIf { it.itemCount > 0 } ?: return@ConfirmationDialog
val text = clip.getItemAt(0).text
errorDialog = loadColorString(text.toString(), prefs)
},
neutralButtonText = "load from clipboard" // todo: this is too long, maybe better if "load file" is changed to "load"?
)
if (errorDialog)
InfoDialog("error") { showLoadDialog = false } // also error dialog to false?
}
}
private fun loadColorString(colorString: String, prefs: SharedPreferences): Boolean {
try {
val that = Json.decodeFromString<SaveThoseColors>(colorString)
val themeName = KeyboardTheme.getUnusedThemeName(that.name ?: "imported colors", prefs)
val colors = that.colors.map { ColorSetting(it.key, it.value.second, it.value.first) }
KeyboardTheme.writeUserColors(prefs, themeName, colors)
KeyboardTheme.writeUserMoreColors(prefs, themeName, that.moreColors)
} catch (e: SerializationException) {
try {
val allColorsStringMap = Json.decodeFromString<Map<String, Int>>(colorString)
val allColors = EnumMap<ColorType, Int>(ColorType::class.java)
var themeName = "imported colors"
allColorsStringMap.forEach {
try {
allColors[ColorType.valueOf(it.key)] = it.value
} catch (_: IllegalArgumentException) {
themeName = decodeBase36(it.key)
}
}
themeName = KeyboardTheme.getUnusedThemeName(themeName, prefs)
KeyboardTheme.writeUserAllColors(prefs, themeName, allColors)
KeyboardTheme.writeUserMoreColors(prefs, themeName, 2)
} catch (e: SerializationException) {
return false
}
}
keyboardNeedsReload = true
return true
}
@Preview

View file

@ -73,12 +73,6 @@ fun ColorsScreen(
isNight: Boolean,
onClickBack: () -> Unit
) {
// todo:
// allow save (load should be in theme selector, maybe here too)
// import/export should now also store theme name
// handle name collisions on load by simply appending a number
// make sure import of old colors works
val ctx = LocalContext.current
// is there really no better way of only setting forceOpposite while the screen is shown (and not paused)?
@ -163,7 +157,7 @@ fun ColorsScreen(
stringResource(R.string.save) to {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_TITLE,"theme.json")
.putExtra(Intent.EXTRA_TITLE,"${newThemeName.text}.json")
.setType("application/json")
saveLauncher.launch(intent)
},