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.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<MutableList<KeyData>> {
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)
}

View file

@ -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) }
}

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.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) {

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.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)

View file

@ -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<Preference>("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())
}

View file

@ -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<String>()
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<TextView>(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<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 {
row.findViewById<View>(R.id.language_details).isGone = true
}
@ -198,18 +198,18 @@ class LanguageSettingsDialog(
row.findViewById<ImageView>(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()
}

View file

@ -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;

View file

@ -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() }
}

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.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<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)) {
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<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()
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
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);
}

View file

@ -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<String>()
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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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()
}

View file

@ -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

View file

@ -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
}

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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))))
}
}