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 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 // returns false if not renamed due to invalid name or collision
fun renameUserColors(from: String, to: String, prefs: SharedPreferences): Boolean { fun renameUserColors(from: String, to: String, prefs: SharedPreferences): Boolean {
if (to.isBlank()) return false // don't want that if (to.isBlank()) return false // don't want that

View file

@ -1,6 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
package helium314.keyboard.settings.dialogs 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.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row 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.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import helium314.keyboard.keyboard.ColorSetting
import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.settings.Settings 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.getStringResourceOrName
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.Setting import helium314.keyboard.settings.Setting
import helium314.keyboard.settings.SettingsDestination import helium314.keyboard.settings.SettingsDestination
import helium314.keyboard.settings.keyboardNeedsReload 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 @Composable
fun ColorThemePickerDialog( fun ColorThemePickerDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
@ -71,16 +85,14 @@ fun ColorThemePickerDialog(
val index = colors.indexOf(selectedColor) val index = colors.indexOf(selectedColor)
if (index != -1) state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3) if (index != -1) state.scrollToItem(index, -state.layoutInfo.viewportSize.height / 3)
} }
var showLoadDialog by remember { mutableStateOf(false) }
ThreeButtonAlertDialog( ThreeButtonAlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
cancelButtonText = stringResource(R.string.dialog_close), cancelButtonText = stringResource(R.string.dialog_close),
onConfirmed = { }, onConfirmed = { },
confirmButtonText = null, confirmButtonText = null,
// neutralButtonText = stringResource(R.string.load), neutralButtonText = stringResource(R.string.load),
onNeutral = { onNeutral = { showLoadDialog = true },
// todo: launcher to select file
// when importing make sure name is not in use
},
title = { Text(setting.title) }, title = { Text(setting.title) },
text = { text = {
CompositionLocalProvider( 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 @Preview

View file

@ -73,12 +73,6 @@ fun ColorsScreen(
isNight: Boolean, isNight: Boolean,
onClickBack: () -> Unit 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 val ctx = LocalContext.current
// is there really no better way of only setting forceOpposite while the screen is shown (and not paused)? // 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 { stringResource(R.string.save) to {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE) .addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_TITLE,"theme.json") .putExtra(Intent.EXTRA_TITLE,"${newThemeName.text}.json")
.setType("application/json") .setType("application/json")
saveLauncher.launch(intent) saveLauncher.launch(intent)
}, },