From aa3bf378520bd13cf14e9b35d6025a07c2318b47 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 15 Feb 2025 11:39:53 +0100 Subject: [PATCH] put LayoutUtilsCustom into an object --- .../internal/keyboard_parser/LayoutParser.kt | 9 +- .../main/java/helium314/keyboard/latin/App.kt | 77 ++-- .../keyboard/latin/RichInputMethodSubtype.kt | 4 +- .../keyboard/latin/common/StringUtils.kt | 5 + .../settings/AdvancedSettingsFragment.kt | 18 +- .../latin/settings/LanguageSettingsDialog.kt | 18 +- .../latin/utils/AdditionalSubtypeUtils.java | 2 +- .../keyboard/latin/utils/LayoutUtils.kt | 2 +- .../keyboard/latin/utils/LayoutUtilsCustom.kt | 409 +++++++++--------- .../latin/utils/SubtypeLocaleUtils.java | 4 +- .../keyboard/latin/utils/SubtypeSettings.kt | 6 +- .../keyboard/latin/utils/SubtypeUtils.kt | 4 +- .../dialogs/ColorThemePickerDialog.kt | 2 +- .../settings/dialogs/LayoutEditDialog.kt | 29 +- .../settings/dialogs/LayoutPickerDialog.kt | 20 +- .../preferences/BackupRestorePreference.kt | 7 +- .../preferences/LayoutEditPreference.kt | 4 +- .../keyboard/settings/screens/ColorsScreen.kt | 2 +- .../keyboard/settings/screens/LayoutScreen.kt | 5 +- .../helium314/keyboard/KeyboardParserTest.kt | 4 +- 20 files changed, 305 insertions(+), 326 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LayoutParser.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LayoutParser.kt index f2aee604..cd3197ff 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LayoutParser.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LayoutParser.kt @@ -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.latin.common.splitOnWhitespace 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.LayoutUtils +import helium314.keyboard.latin.utils.LayoutUtilsCustom import helium314.keyboard.latin.utils.Log -import helium314.keyboard.latin.utils.getCustomLayoutFiles import helium314.keyboard.latin.utils.prefs import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json @@ -76,7 +75,7 @@ object LayoutParser { private fun createCacheLambda(layoutType: LayoutType, layoutName: String, context: Context): (KeyboardParams) -> MutableList> { 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 { val florisKeyData = parseJsonString(layoutFileContent, false) return { params -> @@ -101,8 +100,8 @@ object LayoutParser { } private fun getLayoutFileContent(layoutType: LayoutType, layoutName: String, context: Context): String { - if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) - getCustomLayoutFiles(layoutType, context) + if (LayoutUtilsCustom.isCustomLayout(layoutName)) + LayoutUtilsCustom.getCustomLayoutFiles(layoutType, context) .firstOrNull { it.name.startsWith(layoutName) }?.let { return it.readText() } return LayoutUtils.getContent(layoutType, layoutName, context) } diff --git a/app/src/main/java/helium314/keyboard/latin/App.kt b/app/src/main/java/helium314/keyboard/latin/App.kt index 1de81b04..95f1e2db 100644 --- a/app/src/main/java/helium314/keyboard/latin/App.kt +++ b/app/src/main/java/helium314/keyboard/latin/App.kt @@ -9,21 +9,18 @@ import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode import helium314.keyboard.latin.common.ColorType import helium314.keyboard.latin.common.LocaleUtils.constructLocale +import helium314.keyboard.latin.common.encodeBase36 import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX 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.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.ToolbarKey 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.protectedPrefs 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) { val prefs = context.prefs() 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 upgradesWhenComingFromOldAppName(context) 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()) { - 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 @@ -147,8 +148,8 @@ fun checkVersionUpgrade(context: Context) { Settings.writePrefAdditionalSubtypes(prefs, newSubtypeStrings.joinToString(";")) } // rename other custom layouts - onCustomLayoutFileListChanged() - getCustomLayoutFiles(context).forEach { + LayoutUtilsCustom.onCustomLayoutFileListChanged() + File(DeviceProtectedUtils.getFilesDir(context), "layouts").listFiles()?.forEach { val newFile = getCustomLayoutFile(it.name.substringBeforeLast(".") + ".", context) if (newFile.name == it.name) return@forEach if (newFile.exists()) newFile.delete() // should never happen @@ -243,99 +244,99 @@ fun checkVersionUpgrade(context: Context) { File(DeviceProtectedUtils.getFilesDir(context), "layouts").listFiles()?.forEach { file -> val folder = DeviceProtectedUtils.getFilesDir(context) when (file.name) { - "${CUSTOM_LAYOUT_PREFIX}symbols." -> { + "custom.symbols." -> { val dir = File(folder, LayoutType.SYMBOLS.folder) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("symbols")}." + val name = "custom.${encodeBase36("symbols")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("symbols_shifted")}." + val name = "custom.${encodeBase36("symbols_shifted")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("symbols_arabic")}." + val name = "custom.${encodeBase36("symbols_arabic")}." file.renameTo(File(dir, name)) } - "${CUSTOM_LAYOUT_PREFIX}numpad." -> { + "custom.numpad." -> { val dir = File(folder, LayoutType.NUMPAD.folder) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("numpad")}." + val name = "custom.${encodeBase36("numpad")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("numpad_landscape")}." + val name = "custom.${encodeBase36("numpad_landscape")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("number")}." + val name = "custom.${encodeBase36("number")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("phone")}." + val name = "custom.${encodeBase36("phone")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("phone_symbols")}." + val name = "custom.${encodeBase36("phone_symbols")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("number_row")}." + val name = "custom.${encodeBase36("number_row")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("emoji_bottom_row")}." + val name = "custom.${encodeBase36("emoji_bottom_row")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("clip_bottom_row")}." + val name = "custom.${encodeBase36("clip_bottom_row")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("functional_keys")}." + val name = "custom.${encodeBase36("functional_keys")}." file.renameTo(File(dir, name)) 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) dir.mkdirs() - val name = "$CUSTOM_LAYOUT_PREFIX${encodeBase36("functional_keys_symbols")}." + val name = "custom.${encodeBase36("functional_keys_symbols")}." file.renameTo(File(dir, name)) } - "${CUSTOM_LAYOUT_PREFIX}functional_keys_symbols_shifted." -> { + "custom.functional_keys_symbols_shifted." -> { val dir = File(folder, LayoutType.FUNCTIONAL.folder) 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)) } else -> { @@ -354,7 +355,7 @@ fun checkVersionUpgrade(context: Context) { prefs.edit().putString(Settings.PREF_ADDITIONAL_SUBTYPES, prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, "")!!.replace(":", "ยง")).apply() } upgradeToolbarPrefs(prefs) - onCustomLayoutFileListChanged() // just to be sure + LayoutUtilsCustom.onCustomLayoutFileListChanged() // just to be sure prefs.edit { putInt(Settings.PREF_VERSION_CODE, BuildConfig.VERSION_CODE) } } diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt b/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt index 58ee4ed8..5f5627ae 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt +++ b/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt @@ -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.LocaleUtils.constructLocale 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.LayoutUtilsCustom import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.SubtypeLocaleUtils import helium314.keyboard.latin.utils.locale @@ -38,7 +38,7 @@ class RichInputMethodSubtype private constructor(val rawSubtype: InputMethodSubt /** layout names for this subtype by LayoutType */ 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() { if (isNoLanguage) { diff --git a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt index 98e5beba..8a9c9ddf 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/StringUtils.kt @@ -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.newSingleCodePointString import helium314.keyboard.latin.settings.SpacingAndPunctuations +import java.math.BigInteger import java.util.Locale fun loopOverCodePoints(s: CharSequence, run: (Int) -> Boolean) { @@ -124,6 +125,10 @@ fun String.decapitalize(locale: Locale): String { 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(s: CharSequence): Boolean = mightBeEmoji(s) && s.matches(emoRegex) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt index 373b376e..2dc6cbbc 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt @@ -46,19 +46,11 @@ import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.common.splitOnWhitespace import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy 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.ExecutorUtils import helium314.keyboard.latin.utils.JniUtils 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.onCustomLayoutFileListChanged import helium314.keyboard.latin.utils.reloadEnabledSubtypes import helium314.keyboard.latin.utils.updateAdditionalSubtypes 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 backupFilePatterns by lazy { listOf( "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(), "UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(), "custom_background_image.*".toRegex(), @@ -134,7 +126,7 @@ class AdvancedSettingsFragment : SubScreenFragment() { true } findPreference("custom_functional_key_layouts")?.setOnPreferenceClickListener { - showCustomizeFunctionalKeyLayoutsDialog() +// showCustomizeFunctionalKeyLayoutsDialog() true } @@ -173,7 +165,7 @@ class AdvancedSettingsFragment : SubScreenFragment() { .setNegativeButton(android.R.string.cancel, null) .show() */ } - +/* private fun customizeSymbolNumberLayout(layoutName: String) { val customLayoutName = getCustomLayoutFiles(requireContext()).map { it.name } .firstOrNull { it.startsWith("$CUSTOM_LAYOUT_PREFIX$layoutName.") } @@ -211,7 +203,7 @@ class AdvancedSettingsFragment : SubScreenFragment() { val displayName = layoutName.substringAfter(CUSTOM_LAYOUT_PREFIX).getStringResourceOrName("layout_", requireContext()) editCustomLayout(customLayoutName ?: "$layoutName.", requireContext(), originalLayout, displayName) } - +*/ @SuppressLint("ApplySharedPref") private fun onClickLoadLibrary(): Boolean { // get architecture for telling user which file to use @@ -425,7 +417,7 @@ class AdvancedSettingsFragment : SubScreenFragment() { // reload current prefs screen preferenceScreen.removeAll() setupPreferences() - onCustomLayoutFileListChanged() +// onCustomLayoutFileListChanged() KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext()) } diff --git a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt index 491bf299..4b32bfd5 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt @@ -100,7 +100,7 @@ class LanguageSettingsDialog( } private fun addSubtype(name: String) { - onCustomLayoutFileListChanged() + LayoutUtilsCustom.onCustomLayoutFileListChanged() val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable) val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype) @@ -138,7 +138,7 @@ class LanguageSettingsDialog( val displayNames = mutableListOf() infos.forEach { 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 layouts.add(mainLayoutName) displayNames.add(it.subtype.displayName(context).toString()) @@ -155,7 +155,7 @@ class LanguageSettingsDialog( .setItems(displayNames.toTypedArray()) { di, i -> di.dismiss() 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) } } .setNegativeButton(android.R.string.cancel, null) @@ -163,7 +163,7 @@ class LanguageSettingsDialog( } 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) { @@ -172,9 +172,9 @@ class LanguageSettingsDialog( row.findViewById(R.id.language_name).text = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype) ?: subtype.subtype.displayName(context) - if (layoutSetName.startsWith(CUSTOM_LAYOUT_PREFIX)) { + if (LayoutUtilsCustom.isCustomLayout(layoutSetName)) { row.findViewById(R.id.language_details).setText(R.string.edit_layout) - row.findViewById(R.id.language_text).setOnClickListener { editCustomLayout(layoutSetName, context) } + row.findViewById(R.id.language_text).setOnClickListener { LayoutUtilsCustom.editCustomLayout(layoutSetName, context) } } else { row.findViewById(R.id.language_details).isGone = true } @@ -198,18 +198,18 @@ class LanguageSettingsDialog( row.findViewById(R.id.delete_button).apply { isVisible = true setOnClickListener { - val isCustom = layoutSetName.startsWith(CUSTOM_LAYOUT_PREFIX) + val isCustom = LayoutUtilsCustom.isCustomLayout(layoutSetName) fun delete() { binding.subtypes.removeView(row) infos.remove(subtype) if (isCustom) - removeCustomLayoutFile(layoutSetName, context) + LayoutUtilsCustom.removeCustomLayoutFile(layoutSetName, context) removeAdditionalSubtype(prefs, subtype.subtype) removeEnabledSubtype(prefs, subtype.subtype) reloadSetting() } 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 { delete() } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java index fb996e3d..d32e60c9 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java @@ -122,7 +122,7 @@ public final class AdditionalSubtypeUtils { final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN); // Here we assume that all the additional subtypes are EmojiCapable 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 // layout has been removed. return null; diff --git a/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtils.kt index 517419f9..6d2bf91f 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtils.kt @@ -14,7 +14,7 @@ object LayoutUtils { if (locale == null) return getAllAvailableSubtypes().mapTo(HashSet()) { it.mainLayoutName()?.substringBefore("+") ?: "qwerty" } 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" } return getSubtypesForLocale(locale).mapNotNullTo(HashSet()) { it.mainLayoutName() } } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtilsCustom.kt b/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtilsCustom.kt index 48a1a56a..606ea735 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtilsCustom.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/LayoutUtilsCustom.kt @@ -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.latin.R 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 kotlinx.serialization.SerializationException import java.io.File import java.io.IOException -import java.math.BigInteger import java.util.EnumMap -// todo: object like LayoutUtils - -fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) { - if (uri == null) - return infoDialog(context, context.getString(R.string.layout_error, "layout file not found")) - val layoutContent: String - try { - val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile") - FileUtils.copyContentUriToNewFile(uri, context, tmpFile) - layoutContent = tmpFile.readText() - tmpFile.delete() - } catch (e: IOException) { - 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(".") +object LayoutUtilsCustom { + fun loadCustomLayout(uri: Uri?, languageTag: String, context: Context, onAdded: (String) -> Unit) { + if (uri == null) + return infoDialog(context, context.getString(R.string.layout_error, "layout file not found")) + val layoutContent: String + try { + val tmpFile = File(context.filesDir.absolutePath + File.separator + "tmpfile") + FileUtils.copyContentUriToNewFile(uri, context, tmpFile) + layoutContent = tmpFile.readText() + tmpFile.delete() + } catch (e: IOException) { + return infoDialog(context, context.getString(R.string.layout_error, "cannot read layout file")) } - } - 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}")) + 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 = 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) // ?: return infoDialog(context, context.getString(R.string.layout_error, "invalid layout file, ${Log.getLog(10).lastOrNull { it.tag == TAG }?.message}")) - AlertDialog.Builder(context) - .setTitle(R.string.title_layout_name_select) - .setView(EditText(context).apply { - setText(name) - doAfterTextChanged { name = it.toString() } - val padding = ResourceUtils.toPx(8, context.resources) - setPadding(3 * padding, padding, 3 * padding, padding) - inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL - }) - .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) - 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>): 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 { // 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 = - customLayoutMap.getOrPut(layoutType) { - File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder).listFiles()?.toList() ?: emptyList() - } - -private val customLayoutMap = EnumMap>(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)) { + AlertDialog.Builder(context) + .setTitle(R.string.title_layout_name_select) + .setView(EditText(context).apply { + setText(name) + doAfterTextChanged { name = it.toString() } + val padding = ResourceUtils.toPx(8, context.resources) + setPadding(3 * padding, padding, 3 * padding, padding) + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_NORMAL + }) + .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) + 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>): 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 { // 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 = + customLayoutMap.getOrPut(layoutType) { + File(DeviceProtectedUtils.getFilesDir(context), layoutType.folder).listFiles()?.toList() ?: emptyList() + } + + private val customLayoutMap = EnumMap>(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() + 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? = 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? = 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." diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java index ce30920b..446acff3 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java @@ -266,8 +266,8 @@ public final class SubtypeLocaleUtils { @Nullable public static String getKeyboardLayoutSetDisplayName(@NonNull final String layoutName) { - if (LayoutUtilsCustomKt.isCustomLayout(layoutName)) - return LayoutUtilsCustomKt.getCustomLayoutDisplayName(layoutName); + if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName)) + return LayoutUtilsCustom.INSTANCE.getCustomLayoutDisplayName(layoutName); return sKeyboardLayoutToDisplayNameMap.get(layoutName); } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt index ab6b11cb..3c3abea0 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt @@ -246,14 +246,14 @@ private fun loadResourceSubtypes(resources: Resources) { } // 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 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() additionalSubtypes.forEach { val name = it.substringAfter(":").substringBefore(":") - if (!name.startsWith(CUSTOM_LAYOUT_PREFIX)) return@forEach + if (!LayoutUtilsCustom.isCustomLayout(name)) return@forEach if (name !in customSubtypeFiles) subtypesToRemove.add(it) } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt index 4fb0dd14..acf57734 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt @@ -25,7 +25,7 @@ fun InputMethodSubtype.mainLayoutName(): String? { // todo (later): this should be done properly and in SubtypeLocaleUtils fun InputMethodSubtype.displayName(context: Context): CharSequence { val layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(this) - if (layoutName.startsWith(CUSTOM_LAYOUT_PREFIX)) - return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${getCustomLayoutDisplayName(layoutName)})" + if (LayoutUtilsCustom.isCustomLayout(layoutName)) + return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName)})" return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this) } diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt index 48452361..5a97401b 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/ColorThemePickerDialog.kt @@ -43,9 +43,9 @@ import helium314.keyboard.keyboard.ColorSetting import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.latin.R import helium314.keyboard.latin.common.ColorType +import helium314.keyboard.latin.common.decodeBase36 import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.Log -import helium314.keyboard.latin.utils.decodeBase36 import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.getStringResourceOrName import helium314.keyboard.latin.utils.prefs diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutEditDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutEditDialog.kt index 0d820b61..7696b946 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutEditDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutEditDialog.kt @@ -20,14 +20,9 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.window.DialogProperties import helium314.keyboard.latin.R import helium314.keyboard.latin.utils.LayoutType +import helium314.keyboard.latin.utils.LayoutUtilsCustom 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.isCustomLayout -import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged import helium314.keyboard.settings.keyboardNeedsReload import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -47,17 +42,17 @@ fun LayoutEditDialog( val ctx = LocalContext.current val scope = rememberCoroutineScope() var job: Job? = null - val startIsCustom = isCustomLayout(initialLayoutName) + val startIsCustom = LayoutUtilsCustom.isCustomLayout(initialLayoutName) var displayNameValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue( - if (startIsCustom) getCustomLayoutDisplayName(initialLayoutName) + if (startIsCustom) LayoutUtilsCustom.getCustomLayoutDisplayName(initialLayoutName) else initialLayoutName.getStringResourceOrName("layout_", ctx) )) } val nameValid = displayNameValue.text.isNotBlank() && ( - (startIsCustom && getCustomLayoutName(displayNameValue.text) == initialLayoutName) - || isNameValid(getCustomLayoutName(displayNameValue.text)) + (startIsCustom && LayoutUtilsCustom.getCustomLayoutName(displayNameValue.text) == initialLayoutName) + || isNameValid(LayoutUtilsCustom.getCustomLayoutName(displayNameValue.text)) ) TextInputDialog( @@ -66,15 +61,15 @@ fun LayoutEditDialog( onDismissRequest() }, onConfirmed = { - val newLayoutName = getCustomLayoutName(displayNameValue.text) + val newLayoutName = LayoutUtilsCustom.getCustomLayoutName(displayNameValue.text) if (startIsCustom && initialLayoutName != newLayoutName) - getCustomLayoutFile(initialLayoutName, layoutType, ctx).delete() - getCustomLayoutFile(newLayoutName, layoutType, ctx).writeText(it) - onCustomLayoutFileListChanged() + LayoutUtilsCustom.getCustomLayoutFile(initialLayoutName, layoutType, ctx).delete() + LayoutUtilsCustom.getCustomLayoutFile(newLayoutName, layoutType, ctx).writeText(it) + LayoutUtilsCustom.onCustomLayoutFileListChanged() keyboardNeedsReload = true }, confirmButtonText = stringResource(R.string.save), - initialText = startContent ?: getCustomLayoutFile(initialLayoutName, layoutType, ctx).readText(), + initialText = startContent ?: LayoutUtilsCustom.getCustomLayoutFile(initialLayoutName, layoutType, ctx).readText(), singleLine = false, title = { TextField( @@ -87,13 +82,13 @@ fun LayoutEditDialog( ) }, checkTextValid = { - val valid = checkLayout(it, ctx) + val valid = LayoutUtilsCustom.checkLayout(it, ctx) job?.cancel() if (!valid) { job = scope.launch { delay(3000) val message = Log.getLog(10) - .lastOrNull { it.tag == "CustomLayoutUtils" }?.message + .lastOrNull { it.tag == "LayoutUtilsCustom" }?.message ?.split("\n")?.take(2)?.joinToString("\n") Toast.makeText(ctx, ctx.getString(R.string.layout_error, message), Toast.LENGTH_LONG).show() } diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutPickerDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutPickerDialog.kt index 9de1c623..ca4f7eb9 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutPickerDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/LayoutPickerDialog.kt @@ -43,14 +43,10 @@ import helium314.keyboard.latin.settings.Defaults.default import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.LayoutUtils +import helium314.keyboard.latin.utils.LayoutUtilsCustom import helium314.keyboard.latin.utils.Log -import helium314.keyboard.latin.utils.checkLayout 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.onCustomLayoutFileListChanged import helium314.keyboard.latin.utils.prefs import helium314.keyboard.settings.Setting import helium314.keyboard.settings.SettingsActivity @@ -72,7 +68,7 @@ fun LayoutPickerDialog( val currentLayout = Settings.readDefaultLayoutName(layoutType, prefs) val internalLayouts = LayoutUtils.getAvailableLayouts(layoutType, ctx) // 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 state = rememberLazyListState() @@ -94,7 +90,7 @@ fun LayoutPickerDialog( } cr.openInputStream(uri)?.use { val content = it.reader().readText() - errorDialog = !checkLayout(content, ctx) + errorDialog = !LayoutUtilsCustom.checkLayout(content, ctx) if (!errorDialog) newLayoutDialog = (name ?: layoutType.default) to content } @@ -129,8 +125,8 @@ fun LayoutPickerDialog( prefs.edit().remove(Settings.PREF_LAYOUT_PREFIX + layoutType.name).apply() keyboardNeedsReload = true } - getCustomLayoutFiles(layoutType, ctx).firstOrNull { it.name == deletedLayout }?.delete() - onCustomLayoutFileListChanged() + LayoutUtilsCustom.getCustomLayoutFiles(layoutType, ctx).firstOrNull { it.name == deletedLayout }?.delete() + LayoutUtilsCustom.onCustomLayoutFileListChanged() }, layoutType = layoutType, layoutName = item, @@ -171,7 +167,7 @@ private fun AddLayoutRow(onNewLayout: (String) -> Unit, userLayouts: Collection< singleLine = true ) IconButton( - enabled = textValue.text.isNotEmpty() && getCustomLayoutName(textValue.text) !in userLayouts, + enabled = textValue.text.isNotEmpty() && LayoutUtilsCustom.getCustomLayoutName(textValue.text) !in userLayouts, onClick = { onNewLayout(textValue.text) } ) { Icon(painterResource(R.drawable.ic_edit), null) } } @@ -210,7 +206,7 @@ private fun LayoutItemRow( } ) Text( - text = if (isCustom) getCustomLayoutDisplayName(layoutName) + text = if (isCustom) LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName) else layoutName.getStringResourceOrName("layout_", ctx), style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f), @@ -223,7 +219,7 @@ private fun LayoutItemRow( if (showDeleteDialog) ConfirmationDialog( 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), onConfirmed = { showDeleteDialog = false diff --git a/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt b/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt index 70e47d8e..3e151c17 100644 --- a/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt +++ b/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt @@ -31,12 +31,11 @@ import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX 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.ExecutorUtils +import helium314.keyboard.latin.utils.LayoutUtilsCustom import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.getActivity -import helium314.keyboard.latin.utils.onCustomLayoutFileListChanged import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.protectedPrefs import helium314.keyboard.latin.utils.reloadEnabledSubtypes @@ -63,7 +62,7 @@ fun BackupRestorePreference(setting: Setting) { var error: String? by rememberSaveable { mutableStateOf(null) } val backupFilePatterns by lazy { listOf( "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(), "UserHistoryDictionary.*/UserHistoryDictionary.*\\.(body|header)".toRegex(), "custom_background_image.*".toRegex(), @@ -181,7 +180,7 @@ fun BackupRestorePreference(setting: Setting) { reloadEnabledSubtypes(ctx) val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) ctx.getActivity()?.sendBroadcast(newDictBroadcast) - onCustomLayoutFileListChanged() + LayoutUtilsCustom.onCustomLayoutFileListChanged() (ctx.getActivity() as? SettingsActivity)?.prefChanged?.value = 210 // for settings reload keyboardNeedsReload = true } diff --git a/app/src/main/java/helium314/keyboard/settings/preferences/LayoutEditPreference.kt b/app/src/main/java/helium314/keyboard/settings/preferences/LayoutEditPreference.kt index c1010254..3e46e080 100644 --- a/app/src/main/java/helium314/keyboard/settings/preferences/LayoutEditPreference.kt +++ b/app/src/main/java/helium314/keyboard/settings/preferences/LayoutEditPreference.kt @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only package helium314.keyboard.settings.preferences - +/* import androidx.compose.material3.Text import androidx.compose.runtime.Composable 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.ListPickerDialog import java.io.File -/* + @Composable fun LayoutEditPreference( setting: Setting, diff --git a/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt index 5f40689f..e4d09597 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/ColorsScreen.kt @@ -51,13 +51,13 @@ import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.latin.R import helium314.keyboard.latin.common.ColorType import helium314.keyboard.latin.common.default +import helium314.keyboard.latin.common.encodeBase36 import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.colorPrefsAndResIds import helium314.keyboard.latin.settings.getColorPrefsToHideInitially import helium314.keyboard.latin.utils.Log import helium314.keyboard.latin.utils.ResourceUtils -import helium314.keyboard.latin.utils.encodeBase36 import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.prefs import helium314.keyboard.settings.SearchScreen diff --git a/app/src/main/java/helium314/keyboard/settings/screens/LayoutScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/LayoutScreen.kt index e7095bc7..9e3b2207 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/LayoutScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/LayoutScreen.kt @@ -13,11 +13,10 @@ import helium314.keyboard.latin.R import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.LayoutType 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.getActivity -import helium314.keyboard.latin.utils.getCustomLayoutDisplayName import helium314.keyboard.latin.utils.getStringResourceOrName -import helium314.keyboard.latin.utils.isCustomLayout import helium314.keyboard.latin.utils.prefs import helium314.keyboard.settings.SearchSettingsScreen 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") var showDialog by rememberSaveable { mutableStateOf(false) } 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) Preference( name = setting.title, diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index 6dcbfb9a..c3a05831 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -20,8 +20,8 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodSubtype 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.checkKeys import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner @@ -484,7 +484,7 @@ f""", // no newline at the end // 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].text, keyParams.outputText) - assertTrue(checkKeys(listOf(listOf(keyParams)))) + assertTrue(LayoutUtilsCustom.checkKeys(listOf(listOf(keyParams)))) } }