put LayoutUtilsCustom into an object

This commit is contained in:
Helium314 2025-02-15 11:39:53 +01:00
parent a3e85bc664
commit aa3bf37852
20 changed files with 305 additions and 326 deletions

View file

@ -18,11 +18,10 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.VariationSele
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.toTextKey import helium314.keyboard.keyboard.internal.keyboard_parser.floris.toTextKey
import helium314.keyboard.latin.common.splitOnWhitespace import helium314.keyboard.latin.common.splitOnWhitespace
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutUtils import helium314.keyboard.latin.utils.LayoutUtils
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -76,7 +75,7 @@ object LayoutParser {
private fun createCacheLambda(layoutType: LayoutType, layoutName: String, context: Context): private fun createCacheLambda(layoutType: LayoutType, layoutName: String, context: Context):
(KeyboardParams) -> MutableList<MutableList<KeyData>> { (KeyboardParams) -> MutableList<MutableList<KeyData>> {
val layoutFileContent = getLayoutFileContent(layoutType, layoutName.substringBefore("+"), context).trimStart() val layoutFileContent = getLayoutFileContent(layoutType, layoutName.substringBefore("+"), context).trimStart()
if (layoutFileContent.startsWith("[") || (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX) && layoutFileContent.startsWith("/"))) { if (layoutFileContent.startsWith("[") || (LayoutUtilsCustom.isCustomLayout(layoutName) && layoutFileContent.startsWith("/"))) {
try { try {
val florisKeyData = parseJsonString(layoutFileContent, false) val florisKeyData = parseJsonString(layoutFileContent, false)
return { params -> return { params ->
@ -101,8 +100,8 @@ object LayoutParser {
} }
private fun getLayoutFileContent(layoutType: LayoutType, layoutName: String, context: Context): String { private fun getLayoutFileContent(layoutType: LayoutType, layoutName: String, context: Context): String {
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) if (LayoutUtilsCustom.isCustomLayout(layoutName))
getCustomLayoutFiles(layoutType, context) LayoutUtilsCustom.getCustomLayoutFiles(layoutType, context)
.firstOrNull { it.name.startsWith(layoutName) }?.let { return it.readText() } .firstOrNull { it.name.startsWith(layoutName) }?.let { return it.readText() }
return LayoutUtils.getContent(layoutType, layoutName, context) return LayoutUtils.getContent(layoutType, layoutName, context)
} }

View file

@ -9,21 +9,18 @@ import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
import helium314.keyboard.latin.common.ColorType import helium314.keyboard.latin.common.ColorType
import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.common.encodeBase36
import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.settings.colorPrefsAndResIds import helium314.keyboard.latin.settings.colorPrefsAndResIds
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.DictionaryInfoUtils import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutType.Companion.folder import helium314.keyboard.latin.utils.LayoutType.Companion.folder
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.ToolbarKey import helium314.keyboard.latin.utils.ToolbarKey
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
import helium314.keyboard.latin.utils.encodeBase36
import helium314.keyboard.latin.utils.getCustomLayoutFile
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.latin.utils.protectedPrefs import helium314.keyboard.latin.utils.protectedPrefs
import helium314.keyboard.latin.utils.upgradeToolbarPrefs import helium314.keyboard.latin.utils.upgradeToolbarPrefs
@ -50,6 +47,10 @@ class App : Application() {
} }
} }
// old variant for old folder structure
private fun getCustomLayoutFile(layoutName: String, context: Context): File =
File(File(DeviceProtectedUtils.getFilesDir(context), "layouts"), layoutName)
fun checkVersionUpgrade(context: Context) { fun checkVersionUpgrade(context: Context) {
val prefs = context.prefs() val prefs = context.prefs()
val oldVersion = prefs.getInt(Settings.PREF_VERSION_CODE, 0) val oldVersion = prefs.getInt(Settings.PREF_VERSION_CODE, 0)
@ -67,9 +68,9 @@ fun checkVersionUpgrade(context: Context) {
if (oldVersion == 0) // new install or restoring settings from old app name if (oldVersion == 0) // new install or restoring settings from old app name
upgradesWhenComingFromOldAppName(context) upgradesWhenComingFromOldAppName(context)
if (oldVersion <= 1000) { // upgrade old custom layouts name if (oldVersion <= 1000) { // upgrade old custom layouts name
val oldShiftSymbolsFile = getCustomLayoutFile("${CUSTOM_LAYOUT_PREFIX}shift_symbols", context) val oldShiftSymbolsFile = getCustomLayoutFile("custom.shift_symbols", context)
if (oldShiftSymbolsFile.exists()) { if (oldShiftSymbolsFile.exists()) {
oldShiftSymbolsFile.renameTo(getCustomLayoutFile("${CUSTOM_LAYOUT_PREFIX}symbols_shifted", context)) oldShiftSymbolsFile.renameTo(getCustomLayoutFile("custom.symbols_shifted", context))
} }
// rename subtype setting, and clean old subtypes that might remain in some cases // rename subtype setting, and clean old subtypes that might remain in some cases
@ -147,8 +148,8 @@ fun checkVersionUpgrade(context: Context) {
Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";")) Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";"))
} }
// rename other custom layouts // rename other custom layouts
onCustomLayoutFileListChanged() LayoutUtilsCustom.onCustomLayoutFileListChanged()
getCustomLayoutFiles(context).forEach { File(DeviceProtectedUtils.getFilesDir(context), "layouts").listFiles()?.forEach {
val newFile = getCustomLayoutFile(it.name.substringBeforeLast(".") + ".", context) val newFile = getCustomLayoutFile(it.name.substringBeforeLast(".") + ".", context)
if (newFile.name == it.name) return@forEach if (newFile.name == it.name) return@forEach
if (newFile.exists()) newFile.delete() // should never happen if (newFile.exists()) newFile.delete() // should never happen
@ -243,99 +244,99 @@ fun checkVersionUpgrade(context: Context) {
File(DeviceProtectedUtils.getFilesDir(context), "layouts").listFiles()?.forEach { file -> File(DeviceProtectedUtils.getFilesDir(context), "layouts").listFiles()?.forEach { file ->
val folder = DeviceProtectedUtils.getFilesDir(context) val folder = DeviceProtectedUtils.getFilesDir(context)
when (file.name) { when (file.name) {
"${CUSTOM_LAYOUT_PREFIX}symbols." -> { "custom.symbols." -> {
val dir = File(folder, LayoutType.SYMBOLS.folder) val dir = File(folder, LayoutType.SYMBOLS.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("symbols")}." val name = "custom.${encodeBase36("symbols")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.SYMBOLS.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.SYMBOLS.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}symbols_shifted." -> { "custom.symbols_shifted." -> {
val dir = File(folder, LayoutType.MORE_SYMBOLS.folder) val dir = File(folder, LayoutType.MORE_SYMBOLS.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("symbols_shifted")}." val name = "custom.${encodeBase36("symbols_shifted")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.MORE_SYMBOLS.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.MORE_SYMBOLS.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}symbols_arabic." -> { "custom.symbols_arabic." -> {
val dir = File(folder, LayoutType.SYMBOLS.folder) val dir = File(folder, LayoutType.SYMBOLS.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("symbols_arabic")}." val name = "custom.${encodeBase36("symbols_arabic")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
} }
"${CUSTOM_LAYOUT_PREFIX}numpad." -> { "custom.numpad." -> {
val dir = File(folder, LayoutType.NUMPAD.folder) val dir = File(folder, LayoutType.NUMPAD.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("numpad")}." val name = "custom.${encodeBase36("numpad")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMPAD.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMPAD.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}numpad_landscape." -> { "custom.numpad_landscape." -> {
val dir = File(folder, LayoutType.NUMPAD_LANDSCAPE.folder) val dir = File(folder, LayoutType.NUMPAD_LANDSCAPE.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("numpad_landscape")}." val name = "custom.${encodeBase36("numpad_landscape")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMPAD_LANDSCAPE.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMPAD_LANDSCAPE.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}number." -> { "custom.number." -> {
val dir = File(folder, LayoutType.NUMBER.folder) val dir = File(folder, LayoutType.NUMBER.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("number")}." val name = "custom.${encodeBase36("number")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMBER.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMBER.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}phone." -> { "custom.phone." -> {
val dir = File(folder, LayoutType.PHONE.folder) val dir = File(folder, LayoutType.PHONE.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("phone")}." val name = "custom.${encodeBase36("phone")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.PHONE.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.PHONE.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}phone_symbols." -> { "custom.phone_symbols." -> {
val dir = File(folder, LayoutType.PHONE_SYMBOLS.folder) val dir = File(folder, LayoutType.PHONE_SYMBOLS.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("phone_symbols")}." val name = "custom.${encodeBase36("phone_symbols")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.PHONE_SYMBOLS.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.PHONE_SYMBOLS.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}number_row." -> { "custom.number_row." -> {
val dir = File(folder, LayoutType.NUMBER_ROW.folder) val dir = File(folder, LayoutType.NUMBER_ROW.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("number_row")}." val name = "custom.${encodeBase36("number_row")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMBER_ROW.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.NUMBER_ROW.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}emoji_bottom_row." -> { "custom.emoji_bottom_row." -> {
val dir = File(folder, LayoutType.EMOJI_BOTTOM.folder) val dir = File(folder, LayoutType.EMOJI_BOTTOM.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("emoji_bottom_row")}." val name = "custom.${encodeBase36("emoji_bottom_row")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.EMOJI_BOTTOM.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.EMOJI_BOTTOM.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}clip_bottom_row." -> { "custom.clip_bottom_row." -> {
val dir = File(folder, LayoutType.CLIPBOARD_BOTTOM.folder) val dir = File(folder, LayoutType.CLIPBOARD_BOTTOM.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("clip_bottom_row")}." val name = "custom.${encodeBase36("clip_bottom_row")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.CLIPBOARD_BOTTOM.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.CLIPBOARD_BOTTOM.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}functional_keys." -> { "custom.functional_keys." -> {
val dir = File(folder, LayoutType.FUNCTIONAL.folder) val dir = File(folder, LayoutType.FUNCTIONAL.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("functional_keys")}." val name = "custom.${encodeBase36("functional_keys")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.FUNCTIONAL.name, name).apply() prefs.edit().putString(Settings.PREF_LAYOUT_PREFIX + LayoutType.FUNCTIONAL.name, name).apply()
} }
"${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols." -> { "custom.functional_keys_symbols." -> {
val dir = File(folder, LayoutType.FUNCTIONAL.folder) val dir = File(folder, LayoutType.FUNCTIONAL.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("functional_keys_symbols")}." val name = "custom.${encodeBase36("functional_keys_symbols")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
} }
"${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols_shifted." -> { "custom.functional_keys_symbols_shifted." -> {
val dir = File(folder, LayoutType.FUNCTIONAL.folder) val dir = File(folder, LayoutType.FUNCTIONAL.folder)
dir.mkdirs() dir.mkdirs()
val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("functional_keys_symbols_shifted")}." val name = "custom.${encodeBase36("functional_keys_symbols_shifted")}."
file.renameTo(File(dir, name)) file.renameTo(File(dir, name))
} }
else -> { else -> {
@ -354,7 +355,7 @@ fun checkVersionUpgrade(context: Context) {
prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, "")!!.replace(":", "§")).apply() prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, "")!!.replace(":", "§")).apply()
} }
upgradeToolbarPrefs(prefs) upgradeToolbarPrefs(prefs)
onCustomLayoutFileListChanged() // just to be sure LayoutUtilsCustom.onCustomLayoutFileListChanged() // just to be sure
prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) } prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) }
} }

View file

@ -11,8 +11,8 @@ import helium314.keyboard.latin.common.Constants
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET
import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.common.LocaleUtils.isRtlLanguage import helium314.keyboard.latin.common.LocaleUtils.isRtlLanguage
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.SubtypeLocaleUtils import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.locale import helium314.keyboard.latin.utils.locale
@ -38,7 +38,7 @@ class RichInputMethodSubtype private constructor(val rawSubtype: InputMethodSubt
/** layout names for this subtype by LayoutType */ /** layout names for this subtype by LayoutType */
val layouts = LayoutType.getLayoutMap(getExtraValueOf(KEYBOARD_LAYOUT_SET) ?: "") val layouts = LayoutType.getLayoutMap(getExtraValueOf(KEYBOARD_LAYOUT_SET) ?: "")
val isCustom: Boolean get() = mainLayoutName.startsWith(CUSTOM_LAYOUT_PREFIX) val isCustom: Boolean get() = LayoutUtilsCustom.isCustomLayout(mainLayoutName)
val fullDisplayName: String get() { val fullDisplayName: String get() {
if (isNoLanguage) { if (isNoLanguage) {

View file

@ -6,6 +6,7 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.common.StringUtils.mightBeEmoji import helium314.keyboard.latin.common.StringUtils.mightBeEmoji
import helium314.keyboard.latin.common.StringUtils.newSingleCodePointString import helium314.keyboard.latin.common.StringUtils.newSingleCodePointString
import helium314.keyboard.latin.settings.SpacingAndPunctuations import helium314.keyboard.latin.settings.SpacingAndPunctuations
import java.math.BigInteger
import java.util.Locale import java.util.Locale
fun loopOverCodePoints(s: CharSequence, run: (Int) -> Boolean) { fun loopOverCodePoints(s: CharSequence, run: (Int) -> Boolean) {
@ -124,6 +125,10 @@ fun String.decapitalize(locale: Locale): String {
return replaceFirstChar { it.lowercase(locale) } return replaceFirstChar { it.lowercase(locale) }
} }
fun encodeBase36(string: String): String = BigInteger(string.toByteArray()).toString(36)
fun decodeBase36(string: String) = BigInteger(string, 36).toByteArray().decodeToString()
fun isEmoji(c: Int): Boolean = mightBeEmoji(c) && isEmoji(newSingleCodePointString(c)) fun isEmoji(c: Int): Boolean = mightBeEmoji(c) && isEmoji(newSingleCodePointString(c))
fun isEmoji(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex) fun isEmoji(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex)

View file

@ -46,19 +46,11 @@ import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.common.splitOnWhitespace import helium314.keyboard.latin.common.splitOnWhitespace
import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils import helium314.keyboard.latin.utils.AdditionalSubtypeUtils
import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_NORMAL
import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS
import helium314.keyboard.latin.utils.CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.ExecutorUtils import helium314.keyboard.latin.utils.ExecutorUtils
import helium314.keyboard.latin.utils.JniUtils import helium314.keyboard.latin.utils.JniUtils
import helium314.keyboard.latin.utils.ResourceUtils import helium314.keyboard.latin.utils.ResourceUtils
import helium314.keyboard.latin.utils.editCustomLayout
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.infoDialog import helium314.keyboard.latin.utils.infoDialog
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.latin.utils.reloadEnabledSubtypes import helium314.keyboard.latin.utils.reloadEnabledSubtypes
import helium314.keyboard.latin.utils.updateAdditionalSubtypes import helium314.keyboard.latin.utils.updateAdditionalSubtypes
import java.io.File import java.io.File
@ -76,7 +68,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
private val libfile by lazy { File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME) } private val libfile by lazy { File(requireContext().filesDir.absolutePath + File.separator + JniUtils.JNI_LIB_IMPORT_FILE_NAME) }
private val backupFilePatterns by lazy { listOf( private val backupFilePatterns by lazy { listOf(
"blacklists/.*\\.txt".toRegex(), "blacklists/.*\\.txt".toRegex(),
"layouts/$CUSTOM_LAYOUT_PREFIX+\\..{0,4}".toRegex(), // can't expect a period at the end, as this would break restoring older backups // "layouts/$CUSTOM_LAYOUT_PREFIX+\\..{0,4}".toRegex(), // can't expect a period at the end, as this would break restoring older backups
"dicts/.*/.*user\\.dict".toRegex(), "dicts/.*/.*user\\.dict".toRegex(),
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(), "UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
"custom_background_image.*".toRegex(), "custom_background_image.*".toRegex(),
@ -134,7 +126,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
true true
} }
findPreference<Preference>("custom_functional_key_layouts")?.setOnPreferenceClickListener { findPreference<Preference>("custom_functional_key_layouts")?.setOnPreferenceClickListener {
showCustomizeFunctionalKeyLayoutsDialog() // showCustomizeFunctionalKeyLayoutsDialog()
true true
} }
@ -173,7 +165,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()
*/ } */ }
/*
private fun customizeSymbolNumberLayout(layoutName: String) { private fun customizeSymbolNumberLayout(layoutName: String) {
val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name } val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name }
.firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") } .firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") }
@ -211,7 +203,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext()) val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext())
editCustomLayout(customLayoutName ?: "$layoutName.", requireContext(), originalLayout, displayName) editCustomLayout(customLayoutName ?: "$layoutName.", requireContext(), originalLayout, displayName)
} }
*/
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
private fun onClickLoadLibrary(): Boolean { private fun onClickLoadLibrary(): Boolean {
// get architecture for telling user which file to use // get architecture for telling user which file to use
@ -425,7 +417,7 @@ class AdvancedSettingsFragment : SubScreenFragment() {
// reload current prefs screen // reload current prefs screen
preferenceScreen.removeAll() preferenceScreen.removeAll()
setupPreferences() setupPreferences()
onCustomLayoutFileListChanged() // onCustomLayoutFileListChanged()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext()) KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
} }

View file

@ -100,7 +100,7 @@ class LanguageSettingsDialog(
} }
private fun addSubtype(name: String) { private fun addSubtype(name: String) {
onCustomLayoutFileListChanged() LayoutUtilsCustom.onCustomLayoutFileListChanged()
val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable) val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable)
val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default
val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype) val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype)
@ -138,7 +138,7 @@ class LanguageSettingsDialog(
val displayNames = mutableListOf<String>() val displayNames = mutableListOf<String>()
infos.forEach { infos.forEach {
val mainLayoutName = it.subtype.mainLayoutName() ?: "qwerty" val mainLayoutName = it.subtype.mainLayoutName() ?: "qwerty"
if (!mainLayoutName.startsWith(CUSTOM_LAYOUT_PREFIX) // don't allow copying custom layout (at least for now) if (!LayoutUtilsCustom.isCustomLayout(mainLayoutName) // don't allow copying custom layout (at least for now)
&& !mainLayoutName.endsWith("+")) { // don't allow copying layouts only defined via extra keys && !mainLayoutName.endsWith("+")) { // don't allow copying layouts only defined via extra keys
layouts.add(mainLayoutName) layouts.add(mainLayoutName)
displayNames.add(it.subtype.displayName(context).toString()) displayNames.add(it.subtype.displayName(context).toString())
@ -155,7 +155,7 @@ class LanguageSettingsDialog(
.setItems(displayNames.toTypedArray()) { di, i -> .setItems(displayNames.toTypedArray()) { di, i ->
di.dismiss() di.dismiss()
val fileName = context.assets.list("layouts")?.firstOrNull { it.startsWith(layouts[i]) } ?: return@setItems val fileName = context.assets.list("layouts")?.firstOrNull { it.startsWith(layouts[i]) } ?: return@setItems
loadCustomLayout(context.assets.open("layouts${File.separator}$fileName").reader().readText(), LayoutUtilsCustom.loadCustomLayout(context.assets.open("layouts${File.separator}$fileName").reader().readText(),
displayNames[i], mainLocale.toLanguageTag(), context) { addSubtype(it) } displayNames[i], mainLocale.toLanguageTag(), context) { addSubtype(it) }
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
@ -163,7 +163,7 @@ class LanguageSettingsDialog(
} }
override fun onNewLayoutFile(uri: Uri?) { override fun onNewLayoutFile(uri: Uri?) {
loadCustomLayout(uri, mainLocale.toLanguageTag(), context) { addSubtype(it) } LayoutUtilsCustom.loadCustomLayout(uri, mainLocale.toLanguageTag(), context) { addSubtype(it) }
} }
private fun addSubtypeToView(subtype: SubtypeInfo) { private fun addSubtypeToView(subtype: SubtypeInfo) {
@ -172,9 +172,9 @@ class LanguageSettingsDialog(
row.findViewById<TextView>(R.id.language_name).text = row.findViewById<TextView>(R.id.language_name).text =
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype) SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype)
?: subtype.subtype.displayName(context) ?: subtype.subtype.displayName(context)
if (layoutSetName.startsWith(CUSTOM_LAYOUT_PREFIX)) { if (LayoutUtilsCustom.isCustomLayout(layoutSetName)) {
row.findViewById<TextView>(R.id.language_details).setText(R.string.edit_layout) row.findViewById<TextView>(R.id.language_details).setText(R.string.edit_layout)
row.findViewById<View>(R.id.language_text).setOnClickListener { editCustomLayout(layoutSetName, context) } row.findViewById<View>(R.id.language_text).setOnClickListener { LayoutUtilsCustom.editCustomLayout(layoutSetName, context) }
} else { } else {
row.findViewById<View>(R.id.language_details).isGone = true row.findViewById<View>(R.id.language_details).isGone = true
} }
@ -198,18 +198,18 @@ class LanguageSettingsDialog(
row.findViewById<ImageView>(R.id.delete_button).apply { row.findViewById<ImageView>(R.id.delete_button).apply {
isVisible = true isVisible = true
setOnClickListener { setOnClickListener {
val isCustom = layoutSetName.startsWith(CUSTOM_LAYOUT_PREFIX) val isCustom = LayoutUtilsCustom.isCustomLayout(layoutSetName)
fun delete() { fun delete() {
binding.subtypes.removeView(row) binding.subtypes.removeView(row)
infos.remove(subtype) infos.remove(subtype)
if (isCustom) if (isCustom)
removeCustomLayoutFile(layoutSetName, context) LayoutUtilsCustom.removeCustomLayoutFile(layoutSetName, context)
removeAdditionalSubtype(prefs, subtype.subtype) removeAdditionalSubtype(prefs, subtype.subtype)
removeEnabledSubtype(prefs, subtype.subtype) removeEnabledSubtype(prefs, subtype.subtype)
reloadSetting() reloadSetting()
} }
if (isCustom) { if (isCustom) {
confirmDialog(context, context.getString(R.string.delete_layout, getCustomLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() } confirmDialog(context, context.getString(R.string.delete_layout, LayoutUtilsCustom.getCustomLayoutDisplayName(layoutSetName)), context.getString(R.string.delete)) { delete() }
} else { } else {
delete() delete()
} }

View file

@ -122,7 +122,7 @@ public final class AdditionalSubtypeUtils {
final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN); final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN);
// Here we assume that all the additional subtypes are EmojiCapable // Here we assume that all the additional subtypes are EmojiCapable
final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSetName, asciiCapable); final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSetName, asciiCapable);
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !LayoutUtilsCustomKt.isCustomLayout(keyboardLayoutSetName)) { if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !LayoutUtilsCustom.INSTANCE.isCustomLayout(keyboardLayoutSetName)) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard // Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed. // layout has been removed.
return null; return null;

View file

@ -14,7 +14,7 @@ object LayoutUtils {
if (locale == null) if (locale == null)
return getAllAvailableSubtypes().mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" } return getAllAvailableSubtypes().mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" }
if (locale.script() == ScriptUtils.SCRIPT_LATIN) if (locale.script() == ScriptUtils.SCRIPT_LATIN)
return getAllAvailableSubtypes().filter { it.isAsciiCapable && it.mainLayoutName()?.startsWith(CUSTOM_LAYOUT_PREFIX) == false } return getAllAvailableSubtypes().filter { it.isAsciiCapable && LayoutUtilsCustom.isCustomLayout(it.mainLayoutName() ?: "qwerty") }
.mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" } .mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" }
return getSubtypesForLocale(locale).mapNotNullTo(HashSet()) { it.mainLayoutName() } return getSubtypesForLocale(locale).mapNotNullTo(HashSet()) { it.mainLayoutName() }
} }

View file

@ -18,230 +18,223 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_NORMAL
import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams
import helium314.keyboard.latin.R import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.FileUtils 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.LayoutType.Companion.folder
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.math.BigInteger
import java.util.EnumMap import java.util.EnumMap
// todo: object like LayoutUtils object LayoutUtilsCustom {
fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) {
fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) { if (uri == null)
if (uri == null) return infoDialog(context, context.getString(R.string.layout_error, "layout file not found"))
return infoDialog(context, context.getString(R.string.layout_error, "layout file not found")) val layoutContent: String
val layoutContent: String try {
try { val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile")
val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile") FileUtils.copyContentUriToNewFile(uri, context, tmpFile)
FileUtils.copyContentUriToNewFile(uri, context, tmpFile) layoutContent = tmpFile.readText()
layoutContent = tmpFile.readText() tmpFile.delete()
tmpFile.delete() } catch (e: IOException) {
} catch (e: IOException) { return infoDialog(context, context.getString(R.string.layout_error, "cannot read layout file"))
return infoDialog(context, context.getString(R.string.layout_error, "cannot read layout file"))
}
var name = ""
context.contentResolver.query(uri, null, null, null, null).use {
if (it != null && it.moveToFirst()) {
val idx = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (idx >= 0)
name = it.getString(idx).substringBeforeLast(".")
} }
}
loadCustomLayout(layoutContent, name, languageTag, context, onAdded)
}
fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: String, context: Context, onAdded: (String) -> Unit) { var name = ""
var name = layoutName context.contentResolver.query(uri, null, null, null, null).use {
if (!checkLayout(layoutContent, context)) if (it != null && it.moveToFirst()) {
return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}")) val idx = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (idx >= 0)
name = it.getString(idx).substringBeforeLast(".")
}
}
loadCustomLayout(layoutContent, name, languageTag, context, onAdded)
}
fun loadCustomLayout(layoutContent: String, layoutName: String, languageTag: String, context: Context, onAdded: (String) -> Unit) {
var name = layoutName
if (!checkLayout(layoutContent, context))
return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}"))
// val isJson = checkLayout(layoutContent, context) // val isJson = checkLayout(layoutContent, context)
// ?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}")) // ?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}"))
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setTitle(R.string.title_layout_name_select) .setTitle(R.string.title_layout_name_select)
.setView(EditText(context).apply { .setView(EditText(context).apply {
setText(name) setText(name)
doAfterTextChanged { name = it.toString() } doAfterTextChanged { name = it.toString() }
val padding = ResourceUtils.toPx(8, context.resources) val padding = ResourceUtils.toPx(8, context.resources)
setPadding(3 * padding, padding, 3 * padding, padding) setPadding(3 * padding, padding, 3 * padding, padding)
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL
}) })
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
// name must be encoded to avoid issues with validity of subtype extra string or file name // name must be encoded to avoid issues with validity of subtype extra string or file name
name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}." name = "$CUSTOM_LAYOUT_PREFIX${languageTag}.${encodeBase36(name)}."
val file = getCustomLayoutFile(name, context) val file = getCustomLayoutFile(name, context)
if (file.exists()) if (file.exists())
file.delete()
file.parentFile?.mkdir()
file.writeText(layoutContent)
onAdded(name)
}
.show()
}
fun checkLayout(layoutContent: String, context: Context): Boolean {
val params = KeyboardParams()
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
addLocaleKeyTextsToParams(context, params, POPUP_KEYS_NORMAL)
try {
val keys = LayoutParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
return checkKeys(keys)
} catch (e: SerializationException) {
Log.w(TAG, "json parsing error", e)
if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]") && layoutContent.contains("},"))
return false // we're sure enough it's a json
} catch (e: Exception) {
Log.w(TAG, "json layout parsed, but considered invalid", e)
return false
}
try {
val keys = LayoutParser.parseSimpleString(layoutContent).map { row -> row.map { it.toKeyParams(params) } }
return checkKeys(keys)
} catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) }
if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]")) {
// layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error
try {
LayoutParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
} catch (e: Exception) { Log.w(TAG, "json parsing error", e) }
}
return false
}
fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
if (keys.isEmpty() || keys.any { it.isEmpty() }) {
Log.w(TAG, "empty rows")
return false
}
if (keys.size > 8) {
Log.w(TAG, "too many rows")
return false
}
if (keys.any { row -> row.size > 20 }) {
Log.w(TAG, "too many keys in one row")
return false
}
if (keys.any { row -> row.any {
if ((it.mLabel?.length ?: 0) > 20) {
Log.w(TAG, "too long text on key: ${it.mLabel}")
true
} else false
} }) {
return false
}
if (keys.any { row -> row.any {
if ((it.mPopupKeys?.size ?: 0) > 20) {
Log.w(TAG, "too many popup keys on key ${it.mLabel}")
true
} else false
} }) {
return false
}
if (keys.any { row -> row.any { true == it.mPopupKeys?.any { popupKey ->
if ((popupKey.mLabel?.length ?: 0) > 10) {
Log.w(TAG, "too long text on popup key: ${popupKey.mLabel}")
true
} else false
} } }) {
return false
}
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) {
File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder).listFiles()?.toList() ?: emptyList()
}
private val customLayoutMap = EnumMap<LayoutType, List<File>>(LayoutType::class.java)
fun onCustomLayoutFileListChanged() {
customLayouts = null
customLayoutMap.clear()
}
private fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts")
fun getCustomLayoutDisplayName(layoutName: String) =
try {
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringBeforeLast("."))
} catch (_: NumberFormatException) {
layoutName
}
fun getCustomLayoutName(displayName: String) = CUSTOM_LAYOUT_PREFIX + encodeBase36(displayName) + "."
fun isCustomLayout(layoutName: String) = layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)
fun getCustomLayoutFile(layoutName: String, layoutType: LayoutType, context: Context): File {
val file = File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder + layoutName)
file.parentFile?.mkdirs()
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 editText = EditText(context).apply {
setText(startContent ?: file.readText())
}
val builder = AlertDialog.Builder(context)
.setTitle(getCustomLayoutDisplayName(layoutName))
.setView(editText)
.setPositiveButton(R.string.save) { _, _ ->
val content = editText.text.toString()
if (!checkLayout(content, context)) {
editCustomLayout(layoutName, context, content)
infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message))
} else {
file.parentFile?.mkdir()
file.writeText(content)
onCustomLayoutFileListChanged()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
}
}
.setNegativeButton(android.R.string.cancel, null)
if (displayName != null) {
if (file.exists()) {
builder.setNeutralButton(R.string.delete) { _, _ ->
confirmDialog(context, context.getString(R.string.delete_layout, displayName), context.getString(R.string.delete)) {
file.delete() file.delete()
file.parentFile?.mkdir()
file.writeText(layoutContent)
onAdded(name)
}
.show()
}
fun checkLayout(layoutContent: String, context: Context): Boolean {
val params = KeyboardParams()
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
addLocaleKeyTextsToParams(context, params, POPUP_KEYS_NORMAL)
try {
val keys = LayoutParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
return checkKeys(keys)
} catch (e: SerializationException) {
Log.w(TAG, "json parsing error", e)
if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]") && layoutContent.contains("},"))
return false // we're sure enough it's a json
} catch (e: Exception) {
Log.w(TAG, "json layout parsed, but considered invalid", e)
return false
}
try {
val keys = LayoutParser.parseSimpleString(layoutContent).map { row -> row.map { it.toKeyParams(params) } }
return checkKeys(keys)
} catch (e: Exception) { Log.w(TAG, "error parsing custom simple layout", e) }
if (layoutContent.trimStart().startsWith("[") && layoutContent.trimEnd().endsWith("]")) {
// layout can't be loaded, assume it's json -> load json layout again because the error message shown to the user is from the most recent error
try {
LayoutParser.parseJsonString(layoutContent).map { row -> row.mapNotNull { it.compute(params)?.toKeyParams(params) } }
} catch (e: Exception) { Log.w(TAG, "json parsing error", e) }
}
return false
}
fun checkKeys(keys: List<List<Key.KeyParams>>): Boolean {
if (keys.isEmpty() || keys.any { it.isEmpty() }) {
Log.w(TAG, "empty rows")
return false
}
if (keys.size > 8) {
Log.w(TAG, "too many rows")
return false
}
if (keys.any { row -> row.size > 20 }) {
Log.w(TAG, "too many keys in one row")
return false
}
if (keys.any { row -> row.any {
if ((it.mLabel?.length ?: 0) > 20) {
Log.w(TAG, "too long text on key: ${it.mLabel}")
true
} else false
} }) {
return false
}
if (keys.any { row -> row.any {
if ((it.mPopupKeys?.size ?: 0) > 20) {
Log.w(TAG, "too many popup keys on key ${it.mLabel}")
true
} else false
} }) {
return false
}
if (keys.any { row -> row.any { true == it.mPopupKeys?.any { popupKey ->
if ((popupKey.mLabel?.length ?: 0) > 10) {
Log.w(TAG, "too long text on popup key: ${popupKey.mLabel}")
true
} else false
} } }) {
return false
}
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) {
File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder).listFiles()?.toList() ?: emptyList()
}
private val customLayoutMap = EnumMap<LayoutType, List<File>>(LayoutType::class.java)
fun onCustomLayoutFileListChanged() {
customLayouts = null
customLayoutMap.clear()
}
private fun getCustomLayoutsDir(context: Context) = File(DeviceProtectedUtils.getFilesDir(context), "layouts")
fun getCustomLayoutDisplayName(layoutName: String) =
try {
decodeBase36(layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).substringBeforeLast("."))
} catch (_: NumberFormatException) {
layoutName
}
fun getCustomLayoutName(displayName: String) = CUSTOM_LAYOUT_PREFIX + encodeBase36(displayName) + "."
fun isCustomLayout(layoutName: String) = layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)
fun getCustomLayoutFile(layoutName: String, layoutType: LayoutType, context: Context): File {
val file = File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder + layoutName)
file.parentFile?.mkdirs()
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 editText = EditText(context).apply {
setText(startContent ?: file.readText())
}
val builder = AlertDialog.Builder(context)
.setTitle(getCustomLayoutDisplayName(layoutName))
.setView(editText)
.setPositiveButton(R.string.save) { _, _ ->
val content = editText.text.toString()
if (!checkLayout(content, context)) {
editCustomLayout(layoutName, context, content)
infoDialog(context, context.getString(R.string.layout_error, Log.getLog(10).lastOrNull { it.tag == TAG }?.message))
} else {
file.parentFile?.mkdir()
file.writeText(content)
onCustomLayoutFileListChanged() onCustomLayoutFileListChanged()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context) KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
} }
} }
.setNegativeButton(android.R.string.cancel, null)
if (displayName != null) {
if (file.exists()) {
builder.setNeutralButton(R.string.delete) { _, _ ->
confirmDialog(context, context.getString(R.string.delete_layout, displayName), context.getString(R.string.delete)) {
file.delete()
onCustomLayoutFileListChanged()
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context)
}
}
}
builder.setTitle(displayName)
} }
builder.setTitle(displayName) builder.show()
} }
builder.show()
// 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
} }
fun encodeBase36(string: String): String = BigInteger(string.toByteArray()).toString(36)
fun decodeBase36(string: String) = BigInteger(string, 36).toByteArray().decodeToString()
// this goes into prefs and file names, so do not change!
const val CUSTOM_LAYOUT_PREFIX = "custom."
private const val TAG = "CustomLayoutUtils"
private var customLayouts: List<File>? = null
const val CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED = "${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols_shifted."
const val CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS = "${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols."
const val CUSTOM_FUNCTIONAL_LAYOUT_NORMAL = "${CUSTOM_LAYOUT_PREFIX}functional_keys."

View file

@ -266,8 +266,8 @@ public final class SubtypeLocaleUtils {
@Nullable @Nullable
public static String getKeyboardLayoutSetDisplayName(@NonNull final String layoutName) { public static String getKeyboardLayoutSetDisplayName(@NonNull final String layoutName) {
if (LayoutUtilsCustomKt.isCustomLayout(layoutName)) if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName))
return LayoutUtilsCustomKt.getCustomLayoutDisplayName(layoutName); return LayoutUtilsCustom.INSTANCE.getCustomLayoutDisplayName(layoutName);
return sKeyboardLayoutToDisplayNameMap.get(layoutName); return sKeyboardLayoutToDisplayNameMap.get(layoutName);
} }

View file

@ -246,14 +246,14 @@ private fun loadResourceSubtypes(resources: Resources) {
} }
// remove custom subtypes without a layout file // remove custom subtypes without a layout file
private fun removeInvalidCustomSubtypes(context: Context) { private fun removeInvalidCustomSubtypes(context: Context) { // todo: new layout structure!
val prefs = context.prefs() val prefs = context.prefs()
val additionalSubtypes = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!.split(";") val additionalSubtypes = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!!.split(";")
val customSubtypeFiles by lazy { getCustomLayoutFiles(LayoutType.MAIN, context).map { it.name } } val customSubtypeFiles by lazy { LayoutUtilsCustom.getCustomLayoutFiles(LayoutType.MAIN, context).map { it.name } }
val subtypesToRemove = mutableListOf<String>() val subtypesToRemove = mutableListOf<String>()
additionalSubtypes.forEach { additionalSubtypes.forEach {
val name = it.substringAfter(":").substringBefore(":") val name = it.substringAfter(":").substringBefore(":")
if (!name.startsWith(CUSTOM_LAYOUT_PREFIX)) return@forEach if (!LayoutUtilsCustom.isCustomLayout(name)) return@forEach
if (name !in customSubtypeFiles) if (name !in customSubtypeFiles)
subtypesToRemove.add(it) subtypesToRemove.add(it)
} }

View file

@ -25,7 +25,7 @@ fun InputMethodSubtype.mainLayoutName(): String? {
// todo (later): this should be done properly and in SubtypeLocaleUtils // todo (later): this should be done properly and in SubtypeLocaleUtils
fun InputMethodSubtype.displayName(context: Context): CharSequence { fun InputMethodSubtype.displayName(context: Context): CharSequence {
val layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(this) val layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(this)
if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) if (LayoutUtilsCustom.isCustomLayout(layoutName))
return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${getCustomLayoutDisplayName(layoutName)})" return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName)})"
return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this) return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this)
} }

View file

@ -43,9 +43,9 @@ 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.common.ColorType
import helium314.keyboard.latin.common.decodeBase36
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.decodeBase36
import helium314.keyboard.latin.utils.getActivity 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

View file

@ -20,14 +20,9 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import helium314.keyboard.latin.R import helium314.keyboard.latin.R
import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.checkLayout
import helium314.keyboard.latin.utils.getCustomLayoutFile
import helium314.keyboard.latin.utils.getCustomLayoutDisplayName
import helium314.keyboard.latin.utils.getCustomLayoutName
import helium314.keyboard.latin.utils.getStringResourceOrName import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.isCustomLayout
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.settings.keyboardNeedsReload import helium314.keyboard.settings.keyboardNeedsReload
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -47,17 +42,17 @@ fun LayoutEditDialog(
val ctx = LocalContext.current val ctx = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var job: Job? = null var job: Job? = null
val startIsCustom = isCustomLayout(initialLayoutName) val startIsCustom = LayoutUtilsCustom.isCustomLayout(initialLayoutName)
var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue( mutableStateOf(TextFieldValue(
if (startIsCustom) getCustomLayoutDisplayName(initialLayoutName) if (startIsCustom) LayoutUtilsCustom.getCustomLayoutDisplayName(initialLayoutName)
else initialLayoutName.getStringResourceOrName("layout_", ctx) else initialLayoutName.getStringResourceOrName("layout_", ctx)
)) ))
} }
val nameValid = displayNameValue.text.isNotBlank() val nameValid = displayNameValue.text.isNotBlank()
&& ( && (
(startIsCustom && getCustomLayoutName(displayNameValue.text) == initialLayoutName) (startIsCustom && LayoutUtilsCustom.getCustomLayoutName(displayNameValue.text) == initialLayoutName)
|| isNameValid(getCustomLayoutName(displayNameValue.text)) || isNameValid(LayoutUtilsCustom.getCustomLayoutName(displayNameValue.text))
) )
TextInputDialog( TextInputDialog(
@ -66,15 +61,15 @@ fun LayoutEditDialog(
onDismissRequest() onDismissRequest()
}, },
onConfirmed = { onConfirmed = {
val newLayoutName = getCustomLayoutName(displayNameValue.text) val newLayoutName = LayoutUtilsCustom.getCustomLayoutName(displayNameValue.text)
if (startIsCustom && initialLayoutName != newLayoutName) if (startIsCustom && initialLayoutName != newLayoutName)
getCustomLayoutFile(initialLayoutName, layoutType, ctx).delete() LayoutUtilsCustom.getCustomLayoutFile(initialLayoutName, layoutType, ctx).delete()
getCustomLayoutFile(newLayoutName, layoutType, ctx).writeText(it) LayoutUtilsCustom.getCustomLayoutFile(newLayoutName, layoutType, ctx).writeText(it)
onCustomLayoutFileListChanged() LayoutUtilsCustom.onCustomLayoutFileListChanged()
keyboardNeedsReload = true keyboardNeedsReload = true
}, },
confirmButtonText = stringResource(R.string.save), confirmButtonText = stringResource(R.string.save),
initialText = startContent ?: getCustomLayoutFile(initialLayoutName, layoutType, ctx).readText(), initialText = startContent ?: LayoutUtilsCustom.getCustomLayoutFile(initialLayoutName, layoutType, ctx).readText(),
singleLine = false, singleLine = false,
title = { title = {
TextField( TextField(
@ -87,13 +82,13 @@ fun LayoutEditDialog(
) )
}, },
checkTextValid = { checkTextValid = {
val valid = checkLayout(it, ctx) val valid = LayoutUtilsCustom.checkLayout(it, ctx)
job?.cancel() job?.cancel()
if (!valid) { if (!valid) {
job = scope.launch { job = scope.launch {
delay(3000) delay(3000)
val message = Log.getLog(10) val message = Log.getLog(10)
.lastOrNull { it.tag == "CustomLayoutUtils" }?.message .lastOrNull { it.tag == "LayoutUtilsCustom" }?.message
?.split("\n")?.take(2)?.joinToString("\n") ?.split("\n")?.take(2)?.joinToString("\n")
Toast.makeText(ctx, ctx.getString(R.string.layout_error, message), Toast.LENGTH_LONG).show() Toast.makeText(ctx, ctx.getString(R.string.layout_error, message), Toast.LENGTH_LONG).show()
} }

View file

@ -43,14 +43,10 @@ import helium314.keyboard.latin.settings.Defaults.default
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutUtils import helium314.keyboard.latin.utils.LayoutUtils
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.checkLayout
import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.getCustomLayoutDisplayName
import helium314.keyboard.latin.utils.getCustomLayoutFiles
import helium314.keyboard.latin.utils.getCustomLayoutName
import helium314.keyboard.latin.utils.getStringResourceOrName import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
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.SettingsActivity import helium314.keyboard.settings.SettingsActivity
@ -72,7 +68,7 @@ fun LayoutPickerDialog(
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs) val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
val internalLayouts = LayoutUtils.getAvailableLayouts(layoutType, ctx) val internalLayouts = LayoutUtils.getAvailableLayouts(layoutType, ctx)
// todo: getCustomLayoutFiles does not work nicely for main layout, but currently this dialog is not used for them // todo: getCustomLayoutFiles does not work nicely for main layout, but currently this dialog is not used for them
val customLayouts = getCustomLayoutFiles(layoutType, ctx).map { it.name }.sorted() val customLayouts = LayoutUtilsCustom.getCustomLayoutFiles(layoutType, ctx).map { it.name }.sorted()
val layouts = internalLayouts + customLayouts + "" val layouts = internalLayouts + customLayouts + ""
val state = rememberLazyListState() val state = rememberLazyListState()
@ -94,7 +90,7 @@ fun LayoutPickerDialog(
} }
cr.openInputStream(uri)?.use { cr.openInputStream(uri)?.use {
val content = it.reader().readText() val content = it.reader().readText()
errorDialog = !checkLayout(content, ctx) errorDialog = !LayoutUtilsCustom.checkLayout(content, ctx)
if (!errorDialog) if (!errorDialog)
newLayoutDialog = (name ?: layoutType.default) to content newLayoutDialog = (name ?: layoutType.default) to content
} }
@ -129,8 +125,8 @@ fun LayoutPickerDialog(
prefs.edit().remove(Settings.PREF_LAYOUT_PREFIX + layoutType.name).apply() prefs.edit().remove(Settings.PREF_LAYOUT_PREFIX + layoutType.name).apply()
keyboardNeedsReload = true keyboardNeedsReload = true
} }
getCustomLayoutFiles(layoutType, ctx).firstOrNull { it.name == deletedLayout }?.delete() LayoutUtilsCustom.getCustomLayoutFiles(layoutType, ctx).firstOrNull { it.name == deletedLayout }?.delete()
onCustomLayoutFileListChanged() LayoutUtilsCustom.onCustomLayoutFileListChanged()
}, },
layoutType = layoutType, layoutType = layoutType,
layoutName = item, layoutName = item,
@ -171,7 +167,7 @@ private fun AddLayoutRow(onNewLayout: (String) -> Unit, userLayouts: Collection<
singleLine = true singleLine = true
) )
IconButton( IconButton(
enabled = textValue.text.isNotEmpty() && getCustomLayoutName(textValue.text) !in userLayouts, enabled = textValue.text.isNotEmpty() && LayoutUtilsCustom.getCustomLayoutName(textValue.text) !in userLayouts,
onClick = { onNewLayout(textValue.text) } onClick = { onNewLayout(textValue.text) }
) { Icon(painterResource(R.drawable.ic_edit), null) } ) { Icon(painterResource(R.drawable.ic_edit), null) }
} }
@ -210,7 +206,7 @@ private fun LayoutItemRow(
} }
) )
Text( Text(
text = if (isCustom) getCustomLayoutDisplayName(layoutName) text = if (isCustom) LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName)
else layoutName.getStringResourceOrName("layout_", ctx), else layoutName.getStringResourceOrName("layout_", ctx),
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@ -223,7 +219,7 @@ private fun LayoutItemRow(
if (showDeleteDialog) if (showDeleteDialog)
ConfirmationDialog( ConfirmationDialog(
onDismissRequest = { showDeleteDialog = false }, onDismissRequest = { showDeleteDialog = false },
text = { Text(stringResource(R.string.delete_layout, getCustomLayoutDisplayName(layoutName))) }, text = { Text(stringResource(R.string.delete_layout, LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName))) },
confirmButtonText = stringResource(R.string.delete), confirmButtonText = stringResource(R.string.delete),
onConfirmed = { onConfirmed = {
showDeleteDialog = false showDeleteDialog = false

View file

@ -31,12 +31,11 @@ import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils import helium314.keyboard.latin.utils.AdditionalSubtypeUtils
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.ExecutorUtils import helium314.keyboard.latin.utils.ExecutorUtils
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.latin.utils.protectedPrefs import helium314.keyboard.latin.utils.protectedPrefs
import helium314.keyboard.latin.utils.reloadEnabledSubtypes import helium314.keyboard.latin.utils.reloadEnabledSubtypes
@ -63,7 +62,7 @@ fun BackupRestorePreference(setting: Setting) {
var error: String? by rememberSaveable { mutableStateOf(null) } var error: String? by rememberSaveable { mutableStateOf(null) }
val backupFilePatterns by lazy { listOf( val backupFilePatterns by lazy { listOf(
"blacklists/.*\\.txt".toRegex(), "blacklists/.*\\.txt".toRegex(),
"layouts/$CUSTOM_LAYOUT_PREFIX+\\..{0,4}".toRegex(), // can't expect a period at the end, as this would break restoring older backups "layouts/${LayoutUtilsCustom.CUSTOM_LAYOUT_PREFIX}+\\..{0,4}".toRegex(), // can't expect a period at the end, as this would break restoring older backups
"dicts/.*/.*user\\.dict".toRegex(), "dicts/.*/.*user\\.dict".toRegex(),
"UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(), "UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(),
"custom_background_image.*".toRegex(), "custom_background_image.*".toRegex(),
@ -181,7 +180,7 @@ fun BackupRestorePreference(setting: Setting) {
reloadEnabledSubtypes(ctx) reloadEnabledSubtypes(ctx)
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
ctx.getActivity()?.sendBroadcast(newDictBroadcast) ctx.getActivity()?.sendBroadcast(newDictBroadcast)
onCustomLayoutFileListChanged() LayoutUtilsCustom.onCustomLayoutFileListChanged()
(ctx.getActivity() as? SettingsActivity)?.prefChanged?.value = 210 // for settings reload (ctx.getActivity() as? SettingsActivity)?.prefChanged?.value = 210 // for settings reload
keyboardNeedsReload = true keyboardNeedsReload = true
} }

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
package helium314.keyboard.settings.preferences package helium314.keyboard.settings.preferences
/*
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -14,7 +14,7 @@ import helium314.keyboard.settings.Setting
import helium314.keyboard.settings.dialogs.LayoutEditDialog import helium314.keyboard.settings.dialogs.LayoutEditDialog
import helium314.keyboard.settings.dialogs.ListPickerDialog import helium314.keyboard.settings.dialogs.ListPickerDialog
import java.io.File import java.io.File
/*
@Composable @Composable
fun LayoutEditPreference( fun LayoutEditPreference(
setting: Setting, setting: Setting,

View file

@ -51,13 +51,13 @@ 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.common.ColorType
import helium314.keyboard.latin.common.default import helium314.keyboard.latin.common.default
import helium314.keyboard.latin.common.encodeBase36
import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.colorPrefsAndResIds import helium314.keyboard.latin.settings.colorPrefsAndResIds
import helium314.keyboard.latin.settings.getColorPrefsToHideInitially import helium314.keyboard.latin.settings.getColorPrefsToHideInitially
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ResourceUtils import helium314.keyboard.latin.utils.ResourceUtils
import helium314.keyboard.latin.utils.encodeBase36
import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.SearchScreen import helium314.keyboard.settings.SearchScreen

View file

@ -13,11 +13,10 @@ import helium314.keyboard.latin.R
import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.LayoutType.Companion.displayNameId import helium314.keyboard.latin.utils.LayoutType.Companion.displayNameId
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.getCustomLayoutDisplayName
import helium314.keyboard.latin.utils.getStringResourceOrName import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.isCustomLayout
import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.SearchSettingsScreen import helium314.keyboard.settings.SearchSettingsScreen
import helium314.keyboard.settings.Setting import helium314.keyboard.settings.Setting
@ -51,7 +50,7 @@ fun createLayoutSettings(context: Context) = listOf(
Log.v("irrelevant", "stupid way to trigger recomposition on preference change") Log.v("irrelevant", "stupid way to trigger recomposition on preference change")
var showDialog by rememberSaveable { mutableStateOf(false) } var showDialog by rememberSaveable { mutableStateOf(false) }
val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs) val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs)
val displayName = if (isCustomLayout(currentLayout)) getCustomLayoutDisplayName(currentLayout) val displayName = if (LayoutUtilsCustom.isCustomLayout(currentLayout)) LayoutUtilsCustom.getCustomLayoutDisplayName(currentLayout)
else currentLayout.getStringResourceOrName("layout_", ctx) else currentLayout.getStringResourceOrName("layout_", ctx)
Preference( Preference(
name = setting.title, name = setting.title,

View file

@ -20,8 +20,8 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.LatinIME
import helium314.keyboard.latin.RichInputMethodSubtype import helium314.keyboard.latin.RichInputMethodSubtype
import helium314.keyboard.latin.utils.AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype import helium314.keyboard.latin.utils.AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype
import helium314.keyboard.latin.utils.LayoutUtilsCustom
import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT
import helium314.keyboard.latin.utils.checkKeys
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.Robolectric import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
@ -484,7 +484,7 @@ f""", // no newline at the end
// todo (later): what's wrong with popup order? // todo (later): what's wrong with popup order?
assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first }) assertEquals(expected[index].popups?.sortedBy { it.first }, keyParams.mPopupKeys?.mapNotNull { it.mLabel to it.mCode }?.sortedBy { it.first })
assertEquals(expected[index].text, keyParams.outputText) assertEquals(expected[index].text, keyParams.outputText)
assertTrue(checkKeys(listOf(listOf(keyParams)))) assertTrue(LayoutUtilsCustom.checkKeys(listOf(listOf(keyParams))))
} }
} }