mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-04-23 07:39:11 +00:00
store custom latin layouts with scripts instead of language tag, so they can be used across languages
This commit is contained in:
parent
5ccc117ae1
commit
bccb10ea39
11 changed files with 64 additions and 47 deletions
|
@ -13,8 +13,8 @@ android {
|
|||
applicationId = "helium314.keyboard"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 2304
|
||||
versionName = "2.3+dev3"
|
||||
versionCode = 2305
|
||||
versionName = "2.3+dev4"
|
||||
ndk {
|
||||
abiFilters.clear()
|
||||
abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64"))
|
||||
|
|
|
@ -19,6 +19,9 @@ import helium314.keyboard.latin.utils.DictionaryInfoUtils
|
|||
import helium314.keyboard.latin.utils.LayoutType
|
||||
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
||||
import helium314.keyboard.latin.utils.LayoutUtilsCustom
|
||||
import helium314.keyboard.latin.utils.Log
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.SCRIPT_LATIN
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import helium314.keyboard.latin.utils.ToolbarKey
|
||||
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
|
||||
import helium314.keyboard.latin.utils.prefs
|
||||
|
@ -344,16 +347,33 @@ fun checkVersionUpgrade(context: Context) {
|
|||
val dir = File(folder, LayoutType.MAIN.folder)
|
||||
dir.mkdirs()
|
||||
file.renameTo(File(dir, file.name))
|
||||
// todo: maybe rename to custom.latn.name. instead of custom.en-GB.name. for latin script?
|
||||
// just make sure the subtypes are still working when the file name is different (need to upgrade PREF_ADDITIONAL_SUBTYPES)
|
||||
// also consider name collision when a user has layout with the same name for 2 languages
|
||||
// decode name, append number, encode name
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prefs.contains(Settings.PREF_ADDITIONAL_SUBTYPES))
|
||||
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, "")!!.replace(":", "§")).apply()
|
||||
}
|
||||
if (oldVersion <= 2304) {
|
||||
// rename layout files for latin scripts, and adjust layouts stored in prefs accordingly
|
||||
LayoutUtilsCustom.getCustomLayoutFiles(LayoutType.MAIN, context).forEach {
|
||||
val locale = it.name.substringAfter("custom.").substringBefore(".").constructLocale()
|
||||
if (locale.script() != SCRIPT_LATIN) return@forEach
|
||||
// change language tag to SCRIPT_LATIN, but
|
||||
// avoid overwriting if 2 layouts have a different language tag, but the same name
|
||||
val layoutDisplayName = LayoutUtilsCustom.getSecondaryLayoutDisplayName(it.name)
|
||||
var newFile = File(it.parentFile!!, LayoutUtilsCustom.getMainLayoutName(layoutDisplayName, locale))
|
||||
var i = 1
|
||||
while (newFile.exists()) // make sure name is not already in use, e.g. custom.en.abcd. and custom.it.abcd. would both be custom.Latn.abcd
|
||||
newFile = File(it.parentFile!!, LayoutUtilsCustom.getMainLayoutName(layoutDisplayName + i++, locale))
|
||||
it.renameTo(newFile)
|
||||
// modify prefs
|
||||
listOf(Settings.PREF_ENABLED_SUBTYPES, Settings.PREF_SELECTED_SUBTYPE, Settings.PREF_ADDITIONAL_SUBTYPES).forEach { key ->
|
||||
val value = prefs.getString(key, "")!!
|
||||
if (it.name in value)
|
||||
prefs.edit().putString(key, value.replace(it.name, newFile.name)).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
upgradeToolbarPrefs(prefs)
|
||||
LayoutUtilsCustom.onCustomLayoutFileListChanged() // just to be sure
|
||||
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
|
||||
|
|
|
@ -202,14 +202,14 @@ class LanguageSettingsDialog(
|
|||
fun delete() {
|
||||
binding.subtypes.removeView(row)
|
||||
infos.remove(subtype)
|
||||
if (isCustom)
|
||||
LayoutUtilsCustom.removeCustomLayoutFile(layoutSetName, context)
|
||||
//if (isCustom)
|
||||
// LayoutUtilsCustom.removeCustomLayoutFile(layoutSetName, context)
|
||||
SubtypeUtilsAdditional.removeAdditionalSubtype(prefs, subtype.subtype)
|
||||
removeEnabledSubtype(prefs, subtype.subtype)
|
||||
reloadSetting()
|
||||
}
|
||||
if (isCustom) {
|
||||
confirmDialog(context, context.getString(R.string.delete_layout, LayoutUtilsCustom.getCustomLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
||||
confirmDialog(context, context.getString(R.string.delete_layout, LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
|
||||
} else {
|
||||
delete()
|
||||
}
|
||||
|
|
|
@ -14,11 +14,14 @@ object LayoutUtils {
|
|||
if (locale == null)
|
||||
return getAllAvailableSubtypes().mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" }
|
||||
if (locale.script() == ScriptUtils.SCRIPT_LATIN)
|
||||
return getAllAvailableSubtypes().filter { it.isAsciiCapable && LayoutUtilsCustom.isCustomLayout(it.mainLayoutName() ?: "qwerty") }
|
||||
return getAllAvailableSubtypes().filter { it.isAsciiCapable && !LayoutUtilsCustom.isCustomLayout(it.mainLayoutName() ?: "qwerty") }
|
||||
.mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" }
|
||||
return getSubtypesForLocale(locale).mapNotNullTo(HashSet()) { it.mainLayoutName() }
|
||||
}
|
||||
|
||||
fun getLMainLayoutsForLocales(locales: List<Locale>, context: Context): Collection<String> =
|
||||
locales.flatMapTo(HashSet()) { getAvailableLayouts(LayoutType.MAIN, context, it) }.sorted()
|
||||
|
||||
fun getContent(layoutType: LayoutType, layoutName: String, context: Context): String {
|
||||
val layouts = context.assets.list(layoutType.folder)!!
|
||||
layouts.firstOrNull { it.startsWith("$layoutName.") }
|
||||
|
|
|
@ -21,10 +21,12 @@ import helium314.keyboard.latin.common.FileUtils
|
|||
import helium314.keyboard.latin.common.decodeBase36
|
||||
import helium314.keyboard.latin.common.encodeBase36
|
||||
import helium314.keyboard.latin.utils.LayoutType.Companion.folder
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import kotlinx.serialization.SerializationException
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.EnumMap
|
||||
import java.util.Locale
|
||||
|
||||
object LayoutUtilsCustom {
|
||||
fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) {
|
||||
|
@ -70,7 +72,7 @@ object LayoutUtilsCustom {
|
|||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
// name must be encoded to avoid issues with validity of subtype extra string or file name
|
||||
name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}."
|
||||
val file = getCustomLayoutFile(name, context)
|
||||
val file = getCustomLayoutFile(name, LayoutType.MAIN, context)
|
||||
if (file.exists())
|
||||
file.delete()
|
||||
file.parentFile?.mkdir()
|
||||
|
@ -149,42 +151,37 @@ object LayoutUtilsCustom {
|
|||
return true
|
||||
}
|
||||
|
||||
/** don't rename or delete the file without calling [onCustomLayoutFileListChanged] */
|
||||
fun getCustomLayoutFile(layoutName: String, context: Context) = // todo: remove
|
||||
File(getCustomLayoutsDir(context), layoutName)
|
||||
|
||||
// cache to avoid frequently listing files
|
||||
/** don't rename or delete files without calling [onCustomLayoutFileListChanged] */
|
||||
fun getCustomLayoutFiles(context: Context): List<File> { // todo: remove, AND USE THE NEW THING FOR SUBTYPE SETTINGS
|
||||
customLayouts?.let { return it }
|
||||
val layouts = getCustomLayoutsDir(context).listFiles()?.toList() ?: emptyList()
|
||||
customLayouts = layouts
|
||||
return layouts
|
||||
}
|
||||
|
||||
fun getCustomLayoutFiles(layoutType: LayoutType, context: Context): List<File> =
|
||||
customLayoutMap.getOrPut(layoutType) {
|
||||
fun getCustomLayoutFiles(layoutType: LayoutType, context: Context, locale: Locale? = null): List<File> {
|
||||
val layouts = customLayoutMap.getOrPut(layoutType) {
|
||||
File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder).listFiles()?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
private val customLayoutMap = EnumMap<LayoutType, List<File>>(LayoutType::class.java)
|
||||
if (layoutType != LayoutType.MAIN || locale == null)
|
||||
return layouts
|
||||
if (locale.script() == ScriptUtils.SCRIPT_LATIN)
|
||||
return layouts.filter { it.name.startsWith(CUSTOM_LAYOUT_PREFIX + ScriptUtils.SCRIPT_LATIN + ".") }
|
||||
return layouts.filter { it.name.startsWith(CUSTOM_LAYOUT_PREFIX + locale.toLanguageTag() + ".") }
|
||||
}
|
||||
|
||||
fun onCustomLayoutFileListChanged() {
|
||||
customLayouts = null
|
||||
customLayoutMap.clear()
|
||||
}
|
||||
|
||||
private fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts")
|
||||
|
||||
fun getCustomLayoutDisplayName(layoutName: String) =
|
||||
fun getSecondaryLayoutDisplayName(layoutName: String) =
|
||||
try {
|
||||
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringBeforeLast("."))
|
||||
if (layoutName.count { it == '.' } == 3) // main layout: "custom.<locale or script>.<name>.", other: custom.<name>.
|
||||
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringAfter(".").substringBeforeLast("."))
|
||||
else decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringBeforeLast("."))
|
||||
} catch (_: NumberFormatException) {
|
||||
layoutName
|
||||
}
|
||||
|
||||
fun getCustomLayoutName(displayName: String) = CUSTOM_LAYOUT_PREFIX + encodeBase36(displayName) + "."
|
||||
|
||||
fun getMainLayoutName(displayName: String, locale: Locale) =
|
||||
if (locale.script() == ScriptUtils.SCRIPT_LATIN)
|
||||
CUSTOM_LAYOUT_PREFIX + ScriptUtils.SCRIPT_LATIN + "." + encodeBase36(displayName) + "."
|
||||
else CUSTOM_LAYOUT_PREFIX + locale.toLanguageTag() + "." + encodeBase36(displayName) + "."
|
||||
|
||||
fun isCustomLayout(layoutName: String) = layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)
|
||||
|
||||
fun getCustomLayoutFile(layoutName: String, layoutType: LayoutType, context: Context): File {
|
||||
|
@ -193,17 +190,13 @@ object LayoutUtilsCustom {
|
|||
return file
|
||||
}
|
||||
|
||||
fun removeCustomLayoutFile(layoutName: String, context: Context) {
|
||||
getCustomLayoutFile(layoutName, context).delete()
|
||||
}
|
||||
|
||||
fun editCustomLayout(layoutName: String, context: Context, startContent: String? = null, displayName: CharSequence? = null) {
|
||||
val file = getCustomLayoutFile(layoutName, context)
|
||||
val file = getCustomLayoutFile(layoutName, LayoutType.MAIN, context)
|
||||
val editText = EditText(context).apply {
|
||||
setText(startContent ?: file.readText())
|
||||
}
|
||||
val builder = AlertDialog.Builder(context)
|
||||
.setTitle(getCustomLayoutDisplayName(layoutName))
|
||||
.setTitle(getSecondaryLayoutDisplayName(layoutName))
|
||||
.setView(editText)
|
||||
.setPositiveButton(R.string.save) { _, _ ->
|
||||
val content = editText.text.toString()
|
||||
|
@ -236,5 +229,6 @@ object LayoutUtilsCustom {
|
|||
// this goes into prefs and file names, so do not change!
|
||||
const val CUSTOM_LAYOUT_PREFIX = "custom."
|
||||
private const val TAG = "LayoutUtilsCustom"
|
||||
private var customLayouts: List<File>? = null
|
||||
private val customLayoutMap = EnumMap<LayoutType, List<File>>(LayoutType::class.java)
|
||||
|
||||
}
|
||||
|
|
|
@ -267,7 +267,7 @@ public final class SubtypeLocaleUtils {
|
|||
@Nullable
|
||||
public static String getMainLayoutDisplayName(@NonNull final String layoutName) {
|
||||
if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName))
|
||||
return LayoutUtilsCustom.INSTANCE.getCustomLayoutDisplayName(layoutName);
|
||||
return LayoutUtilsCustom.INSTANCE.getSecondaryLayoutDisplayName(layoutName);
|
||||
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ fun getMatchingLayoutSetNameForLocale(locale: Locale): String {
|
|||
fun addEnabledSubtype(prefs: SharedPreferences, newSubtype: InputMethodSubtype) {
|
||||
require(initialized)
|
||||
val subtypeString = newSubtype.prefString()
|
||||
val oldSubtypeStrings = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, "")!!.split(SUBTYPE_SEPARATOR)
|
||||
val oldSubtypeStrings = prefs.getString(Settings.PREF_ENABLED_SUBTYPES, Defaults.PREF_ENABLED_SUBTYPES)!!.split(SUBTYPE_SEPARATOR)
|
||||
val newString = (oldSubtypeStrings + subtypeString).filter { it.isNotBlank() }.toSortedSet().joinToString(SUBTYPE_SEPARATOR)
|
||||
prefs.edit { putString(Settings.PREF_ENABLED_SUBTYPES, newString) }
|
||||
|
||||
|
|
|
@ -26,6 +26,6 @@ fun InputMethodSubtype.mainLayoutName(): String? {
|
|||
fun InputMethodSubtype.displayName(context: Context): CharSequence {
|
||||
val layoutName = SubtypeLocaleUtils.getMainLayoutName(this)
|
||||
if (LayoutUtilsCustom.isCustomLayout(layoutName))
|
||||
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName)})"
|
||||
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutName)})"
|
||||
return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ fun LayoutEditDialog(
|
|||
val startIsCustom = LayoutUtilsCustom.isCustomLayout(initialLayoutName)
|
||||
var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(TextFieldValue(
|
||||
if (startIsCustom) LayoutUtilsCustom.getCustomLayoutDisplayName(initialLayoutName)
|
||||
if (startIsCustom) LayoutUtilsCustom.getSecondaryLayoutDisplayName(initialLayoutName)
|
||||
else initialLayoutName.getStringResourceOrName("layout_", ctx)
|
||||
))
|
||||
}
|
||||
|
|
|
@ -206,7 +206,7 @@ private fun LayoutItemRow(
|
|||
}
|
||||
)
|
||||
Text(
|
||||
text = if (isCustom) LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName)
|
||||
text = if (isCustom) LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutName)
|
||||
else layoutName.getStringResourceOrName("layout_", ctx),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f),
|
||||
|
@ -219,7 +219,7 @@ private fun LayoutItemRow(
|
|||
if (showDeleteDialog)
|
||||
ConfirmationDialog(
|
||||
onDismissRequest = { showDeleteDialog = false },
|
||||
text = { Text(stringResource(R.string.delete_layout, LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName))) },
|
||||
text = { Text(stringResource(R.string.delete_layout, LayoutUtilsCustom.getSecondaryLayoutDisplayName(layoutName))) },
|
||||
confirmButtonText = stringResource(R.string.delete),
|
||||
onConfirmed = {
|
||||
showDeleteDialog = false
|
||||
|
|
|
@ -50,7 +50,7 @@ fun createLayoutSettings(context: Context) = listOf(
|
|||
Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
|
||||
var showDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
|
||||
val displayName = if (LayoutUtilsCustom.isCustomLayout(currentLayout)) LayoutUtilsCustom.getCustomLayoutDisplayName(currentLayout)
|
||||
val displayName = if (LayoutUtilsCustom.isCustomLayout(currentLayout)) LayoutUtilsCustom.getSecondaryLayoutDisplayName(currentLayout)
|
||||
else currentLayout.getStringResourceOrName("layout_", ctx)
|
||||
Preference(
|
||||
name = setting.title,
|
||||
|
|
Loading…
Add table
Reference in a new issue