Move layout-language mapping to mapping.yaml, update AddLanguage screen

This commit is contained in:
Aleksandras Kostarevas 2024-09-19 12:21:09 +03:00
parent c1d0787395
commit 6bcc162f1e
10 changed files with 725 additions and 183 deletions

View File

@ -1,5 +1,5 @@
name: Serbian QWERTZ name: Serbian QWERTZ
languages: sr-ZZ languages: sr-Latn
rows: rows:
- letters: q w e r t z u i o p š - letters: q w e r t z u i o p š
- letters: a s d f g h j k l č ć - letters: a s d f g h j k l č ć

View File

@ -1,4 +1,4 @@
name: Русский name: East Slavic
languages: ru be bg bn kk ky uk languages: ru be bg bn kk ky uk
rows: rows:
- letters: й ц у к е н г ш щ з х - letters: й ц у к е н г ш щ з х

View File

@ -0,0 +1,521 @@
languages:
af:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
ar:
- arabic
- lulua
az_AZ:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
be_BY:
- east_slavic
- russian_student
- russian_yavert
- russian_yazhert
bg:
- bulgarian
- bulgarian_bds
bn_BD:
- bengali_akkhor
bn_IN:
- bengali
ca:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
ckb:
- kurdish
cs:
- qwertz
- qwerty
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
da:
- nordic
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
de:
- german
- qwertz
- swiss
- qwerty
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
de_CH:
- swiss
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
el:
- greek
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
en_GB:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
en_IN:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
en_US:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
eo:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
es:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
es_419:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
es_US:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
et_EE:
- nordic
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
eu_ES:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
fa:
- farsi
fi:
- nordic
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
fr:
- azerty
- swiss
- qwerty
- qwertz
- dvorak
- colemak
- bepo
- pcqwerty
- nordic
fr_CA:
- qwerty
- swiss
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
fr_CH:
- swiss
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
gl_ES:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
hi:
- hindi
- hindi_compact
hi_Latn:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
hr:
- qwertz
- qwerty
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
hu:
- qwertz
- qwerty
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
hy_AM:
- armenian_phonetic
in:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
is:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
it:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
it_CH:
- swiss
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
iw:
- hebrew
ka_GE:
- georgian
kk:
- east_slavic
- russian_student
- russian_yavert
- russian_yazhert
km_KH:
- khmer
kn_IN:
- kannada
ky:
- east_slavic
- russian_student
- russian_yavert
- russian_yazhert
lo_LA:
- lao
lt:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
lv:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
mk:
- south_slavic
ml_IN:
- malayalam
mn_MN:
- mongolian
mr_IN:
- marathi
ms_MY:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
nb:
- nordic
ne_NP:
- nepali_romanized
- nepali_traditional
nl:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
nl_BE:
- azerty
- qwerty
- qwertz
- dvorak
- colemak
- bepo
- pcqwerty
- nordic
pl:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
pt_BR:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
pt_PT:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
ro:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
ru:
- east_slavic
- russian_student
- russian_yavert
- russian_yazhert
si_LK:
- sinhala
sk:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
sl:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
sr:
- south_slavic
sr_Latn:
- serbian_qwertz
sv:
- nordic
sw:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
ta_IN:
- tamil
ta_LK:
- tamil
ta_SG:
- tamil
te_IN:
- telugu
th:
- thai
tl:
- spanish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
tr:
- turkish
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
uk:
- east_slavic
- russian_student
- russian_yavert
- russian_yazhert
uz_UZ:
- uzbek
vi:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
zu:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
zz:
- qwerty
- qwertz
- dvorak
- azerty
- colemak
- bepo
- pcqwerty
- nordic
ipa:
- ipa

View File

@ -43,10 +43,10 @@ import org.futo.inputmethod.latin.uix.setSettingBlocking
import org.futo.inputmethod.latin.uix.settings.NavigationItem import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.SettingsActivity import org.futo.inputmethod.latin.uix.settings.SettingsActivity
import org.futo.inputmethod.latin.uix.settings.pages.LocaleLayoutMap
import org.futo.inputmethod.latin.uix.settings.useDataStoreValue import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
import org.futo.inputmethod.latin.uix.theme.Typography import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils
import org.futo.inputmethod.v2keyboard.LayoutManager
import java.util.Locale import java.util.Locale
import kotlin.math.sign import kotlin.math.sign
@ -110,7 +110,7 @@ object Subtypes {
var numAdded = 0 var numAdded = 0
for(i in 0 until locales.size()) { for(i in 0 until locales.size()) {
val locale = locales.get(i).stripExtensionsIfNeeded() val locale = locales.get(i).stripExtensionsIfNeeded()
val layout = findClosestLocaleLayouts(locale).firstOrNull() ?: continue val layout = findClosestLocaleLayouts(context, locale).firstOrNull() ?: continue
addLanguage(context, locale, layout) addLanguage(context, locale, layout)
numAdded += 1 numAdded += 1
@ -124,10 +124,8 @@ object Subtypes {
context.setSettingBlocking(ActiveSubtype.key, context.getSettingBlocking(SubtypesSetting).firstOrNull() ?: return) context.setSettingBlocking(ActiveSubtype.key, context.getSettingBlocking(SubtypesSetting).firstOrNull() ?: return)
} }
fun findClosestLocaleLayouts(locale: Locale): List<String> { fun findClosestLocaleLayouts(context: Context, locale: Locale): List<String> {
val supportedLocales = LocaleLayoutMap.toList().associate { val supportedLocales = LayoutManager.getLayoutMapping(context)
getLocale(it.first) to it.second
}
val perfectMatch = supportedLocales.keys.find { it.language == locale.language && it.country == locale.country } val perfectMatch = supportedLocales.keys.find { it.language == locale.language && it.country == locale.country }
val languageMatch = supportedLocales.keys.find { it.language == locale.language } val languageMatch = supportedLocales.keys.find { it.language == locale.language }
@ -242,7 +240,7 @@ object Subtypes {
for(i in 0 until locales.size()) { for(i in 0 until locales.size()) {
val locale = locales.get(i).stripExtensionsIfNeeded() val locale = locales.get(i).stripExtensionsIfNeeded()
val layout = findClosestLocaleLayouts(locale).firstOrNull() ?: continue val layout = findClosestLocaleLayouts(context, locale).firstOrNull() ?: continue
val value = subtypeToString( val value = subtypeToString(
InputMethodSubtypeBuilder() InputMethodSubtypeBuilder()

View File

@ -67,22 +67,17 @@ fun KeyboardViewCompose(keyboard: Keyboard?, width: Dp) {
} }
@Composable @Composable
fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp) { fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp, locale: Locale? = null) {
val context = LocalContext.current val context = LocalContext.current
val locale = remember(id) { val loc = remember(id) {
LayoutManager.getLayout(context, id).languages.firstOrNull()?.let { locale ?: LayoutManager.getLayout(context, id).languages.firstOrNull()?.let {
val l = Locale.forLanguageTag(it) Locale.forLanguageTag(it)
println("SET LOCALE: $it to $l, ${l.language}, ${l.country}")
l
} }
} }
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE val isLandscape = false//configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
val widthPx: Int val widthPx: Int
val heightPx: Int val heightPx: Int
@ -116,7 +111,7 @@ fun KeyboardLayoutPreview(id: String, width: Dp = 172.dp) {
height = heightPx, height = heightPx,
gap = 4.0f, gap = 4.0f,
keyboardLayoutSet = id, keyboardLayoutSet = id,
locale = locale ?: Locale.ENGLISH, locale = loc ?: Locale.ENGLISH,
editorInfo = editorInfo, editorInfo = editorInfo,
numberRow = numberRow, numberRow = numberRow,
useSplitLayout = isLandscape, useSplitLayout = isLandscape,

View File

@ -130,19 +130,15 @@ private fun levenshteinDistance(lhs: CharSequence, rhs: CharSequence): Int {
return cost[lhsLen] return cost[lhsLen]
} }
private fun <T> List<T>.search(searchTarget: String, keyFunction: (T) -> String): List<T> { fun <T> List<T>.searchMultiple(searchTarget: String, maxDistance: Int = searchTarget.length * 2 / 3, limitLength: Boolean = false, keyFunction: (T) -> List<String>): List<T> {
val maxDistance = searchTarget.length * 2 / 3
return this.mapNotNull { item -> return this.mapNotNull { item ->
val key = keyFunction(item) val keys = keyFunction(item).let {
val distance = levenshteinDistance(searchTarget, key) if(limitLength) {
if (distance <= maxDistance) Pair(item, distance) else null it.map { it.substring(0 until searchTarget.length.coerceAtMost(it.length)) }
}.sortedBy { it.second }.map { it.first } } else {
} it
}
private fun <T> List<T>.searchMultiple(searchTarget: String, keyFunction: (T) -> List<String>): List<T> { }
val maxDistance = searchTarget.length * 2 / 3
return this.mapNotNull { item ->
val keys = keyFunction(item)
val minDistanceKey = keys.minByOrNull { levenshteinDistance(searchTarget, it) } val minDistanceKey = keys.minByOrNull { levenshteinDistance(searchTarget, it) }
val minDistance = minDistanceKey?.let { levenshteinDistance(searchTarget, it) } val minDistance = minDistanceKey?.let { levenshteinDistance(searchTarget, it) }
if (minDistance != null && minDistance <= maxDistance) Pair(item, minDistance) else null if (minDistance != null && minDistance <= maxDistance) Pair(item, minDistance) else null

View File

@ -12,7 +12,6 @@ import androidx.navigation.compose.rememberNavController
import org.futo.inputmethod.latin.R import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.uix.ErrorDialog import org.futo.inputmethod.latin.uix.ErrorDialog
import org.futo.inputmethod.latin.uix.InfoDialog import org.futo.inputmethod.latin.uix.InfoDialog
import org.futo.inputmethod.latin.uix.settings.pages.AddLanguageScreen
import org.futo.inputmethod.latin.uix.settings.pages.AdvancedParametersScreen import org.futo.inputmethod.latin.uix.settings.pages.AdvancedParametersScreen
import org.futo.inputmethod.latin.uix.settings.pages.BlacklistScreen import org.futo.inputmethod.latin.uix.settings.pages.BlacklistScreen
import org.futo.inputmethod.latin.uix.settings.pages.CreditsScreen import org.futo.inputmethod.latin.uix.settings.pages.CreditsScreen
@ -25,6 +24,8 @@ import org.futo.inputmethod.latin.uix.settings.pages.LanguagesScreen
import org.futo.inputmethod.latin.uix.settings.pages.PaymentScreen import org.futo.inputmethod.latin.uix.settings.pages.PaymentScreen
import org.futo.inputmethod.latin.uix.settings.pages.PaymentThankYouScreen import org.futo.inputmethod.latin.uix.settings.pages.PaymentThankYouScreen
import org.futo.inputmethod.latin.uix.settings.pages.PredictiveTextScreen import org.futo.inputmethod.latin.uix.settings.pages.PredictiveTextScreen
import org.futo.inputmethod.latin.uix.settings.pages.SelectLanguageScreen
import org.futo.inputmethod.latin.uix.settings.pages.SelectLayoutsScreen
import org.futo.inputmethod.latin.uix.settings.pages.ThemeScreen import org.futo.inputmethod.latin.uix.settings.pages.ThemeScreen
import org.futo.inputmethod.latin.uix.settings.pages.VoiceInputScreen import org.futo.inputmethod.latin.uix.settings.pages.VoiceInputScreen
import org.futo.inputmethod.latin.uix.settings.pages.addModelManagerNavigation import org.futo.inputmethod.latin.uix.settings.pages.addModelManagerNavigation
@ -53,8 +54,8 @@ fun SettingsNavigator(
) { ) {
composable("home") { HomeScreen(navController) } composable("home") { HomeScreen(navController) }
composable("languages") { LanguagesScreen(navController) } composable("languages") { LanguagesScreen(navController) }
composable("addLanguage") { AddLanguageScreen(navController) } composable("addLanguage") { SelectLanguageScreen(navController) }
composable("addLayout/{lang}") { AddLanguageScreen(navController, it.arguments?.getString("lang")?.urlDecode()) } composable("addLayout/{lang}") { SelectLayoutsScreen(navController, it.arguments?.getString("lang")?.urlDecode() ?: "") }
composable("predictiveText") { PredictiveTextScreen(navController) } composable("predictiveText") { PredictiveTextScreen(navController) }
composable("advancedparams") { AdvancedParametersScreen(navController) } composable("advancedparams") { AdvancedParametersScreen(navController) }
addTypingNavigation(navController) addTypingNavigation(navController)
@ -87,8 +88,4 @@ fun SettingsNavigator(
} }
addModelManagerNavigation(navController) addModelManagerNavigation(navController)
} }
LaunchedEffect(Unit) {
navController.navigate("devlayouts")
}
} }

View File

@ -1,173 +1,184 @@
package org.futo.inputmethod.latin.uix.settings.pages package org.futo.inputmethod.latin.uix.settings.pages
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PlatformImeOptions
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import org.futo.inputmethod.latin.Subtypes import org.futo.inputmethod.latin.Subtypes
import org.futo.inputmethod.latin.stripExtensionsIfNeeded import org.futo.inputmethod.latin.uix.KeyboardLayoutPreview
import org.futo.inputmethod.latin.uix.settings.DropDownPicker import org.futo.inputmethod.latin.uix.actions.searchMultiple
import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScrollableList import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.SettingItem import org.futo.inputmethod.latin.uix.urlEncode
import org.futo.inputmethod.v2keyboard.LayoutManager
import java.text.Normalizer
import java.util.Locale
val QwertyVariants = listOf("qwerty", "qwertz", "dvorak", "azerty", "colemak", "bepo", "pcqwerty", "nordic") private val alphaRegex = Regex("[^a-zA-Z\\s]")
fun normalize(str: String): String {
fun makeQwertyWithPrimary(primary: String): List<String> { return Normalizer.normalize(str, Normalizer.Form.NFD)
return listOf(primary) + QwertyVariants.filter { it != primary } .replace(alphaRegex, "")
} }
fun makeQwertyWithPrimary(primary: String, secondary: String): List<String> {
return listOf(primary, secondary) + QwertyVariants.filter { it != primary && it != secondary }
}
val LocaleLayoutMap = mapOf(
"af" to QwertyVariants,
"ar" to listOf("arabic"),
"az_AZ" to QwertyVariants,
"be_BY" to listOf("east_slavic", "east_slavic_phonetic"),
"bg" to listOf("bulgarian", "bulgarian_bds"),
"bn_BD" to listOf("bengali_akkhor"),
"bn_IN" to listOf("bengali"),
"ca" to makeQwertyWithPrimary("spanish"),
"cs" to makeQwertyWithPrimary("qwertz"),
"da" to makeQwertyWithPrimary("nordic"),
"de" to makeQwertyWithPrimary("qwertz"),
"de_CH" to makeQwertyWithPrimary("swiss"),
"el" to makeQwertyWithPrimary("greek"),
"en_IN" to QwertyVariants,
"en_US" to QwertyVariants,
"en_GB" to QwertyVariants,
"eo" to makeQwertyWithPrimary("spanish"),
"es" to makeQwertyWithPrimary("spanish"),
"es_US" to makeQwertyWithPrimary("spanish"),
"es_419" to makeQwertyWithPrimary("spanish"),
"et_EE" to makeQwertyWithPrimary("nordic"),
"eu_ES" to makeQwertyWithPrimary("spanish"),
"fa" to listOf("farsi"),
"ckb" to listOf("kurdish"),
"fi" to makeQwertyWithPrimary("nordic"),
"fr" to makeQwertyWithPrimary("azerty", "swiss"),
"fr_CA" to makeQwertyWithPrimary("qwerty", "swiss"),
"fr_CH" to makeQwertyWithPrimary("swiss"),
"gl_ES" to makeQwertyWithPrimary("spanish"),
"hi" to listOf("hindi", "hindi_compact"),
//"hi_ZZ" to QwertyVariants,
"hr" to makeQwertyWithPrimary("qwertz"),
"hu" to makeQwertyWithPrimary("qwertz"),
"hy_AM" to listOf("armenian_phonetic"),
"in" to QwertyVariants,
"is" to QwertyVariants,
"it" to QwertyVariants,
"it_CH" to makeQwertyWithPrimary("swiss"),
"iw" to listOf("hebrew"),
"ka_GE" to listOf("georgian"),
"kk" to listOf("east_slavic", "east_slavic_phonetic"),
"km_KH" to listOf("khmer"),
"kn_IN" to listOf("kannada"),
"ky" to listOf("east_slavic", "east_slavic_phonetic"),
"lo_LA" to listOf("lao"),
"lt" to QwertyVariants,
"lv" to QwertyVariants,
"mk" to listOf("south_slavic"),
"ml_IN" to listOf("malayalam"),
"mn_MN" to listOf("mongolian"),
"mr_IN" to listOf("marathi"),
"ms_MY" to QwertyVariants,
"nb" to listOf("nordic"),
"ne_NP" to listOf("nepali_romanized", "nepali_traditional"),
"nl" to QwertyVariants,
"nl_BE" to makeQwertyWithPrimary("azerty"),
"pl" to QwertyVariants,
"pt_BR" to QwertyVariants,
"pt_PT" to QwertyVariants,
"ro" to QwertyVariants,
"ru" to listOf("east_slavic", "east_slavic_phonetic"),
"si_LK" to listOf("sinhala"),
"sk" to QwertyVariants,
"sl" to QwertyVariants,
"sr" to listOf("south_slavic"),
"sr_ZZ" to listOf("serbian_qwertz"),
"sv" to listOf("nordic"),
"sw" to QwertyVariants,
"ta_IN" to listOf("tamil"),
"ta_LK" to listOf("tamil"),
"ta_SG" to listOf("tamil"),
"te_IN" to listOf("telugu"),
"th" to listOf("thai"),
"tl" to makeQwertyWithPrimary("spanish"),
"tr" to makeQwertyWithPrimary("turkish"),
"uk" to listOf("east_slavic", "east_slavic_phonetic"),
"uz_UZ" to listOf("uzbek"),
"vi" to QwertyVariants,
"zu" to QwertyVariants,
"zz" to QwertyVariants,
)
@Preview @Preview
@Composable @Composable
fun AddLanguageScreen(navController: NavHostController = rememberNavController(), defaultLocale: String? = null) { fun SelectLanguageScreen(navController: NavHostController = rememberNavController()) {
val context = LocalContext.current val context = LocalContext.current
val defaultLocaleVal = defaultLocale ?: context.resources.configuration.locale.stripExtensionsIfNeeded().toString() val layoutMapping = remember { LayoutManager.getLayoutMapping(context) }
val defaultLayout = Subtypes.findClosestLocaleLayouts(Subtypes.getLocale(defaultLocaleVal)).firstOrNull() ?: "qwerty"
val selectedLocale: MutableState<String> = remember { mutableStateOf(defaultLocale ?: context.resources.configuration.locale.stripExtensionsIfNeeded().toString()) } val systemLocale = remember { context.resources.configuration.locales[0] }
val selectedLayout: MutableState<String> = remember { mutableStateOf(defaultLayout) } val textFieldValue = remember { mutableStateOf(TextFieldValue("")) }
val keys = remember { LocaleLayoutMap.keys.toList() } val locales = remember {
ScrollableList { layoutMapping.keys.toList().sortedBy {
ScreenTitle("Add Language", showBack = true, navController) it.getDisplayName(systemLocale)
}
SettingItem(title = "Language") {
DropDownPicker(
"",
keys,
selectedLocale.value,
{
selectedLocale.value = it
selectedLayout.value = LocaleLayoutMap[it]!!.first()
},
{
Subtypes.getNameForLocale(it)
},
modifier = Modifier.width(180.dp)
)
} }
SettingItem(title = "Layout") { val searchKeys = if(textFieldValue.value.text.isEmpty()) {
DropDownPicker( locales
"", } else {
LocaleLayoutMap[selectedLocale.value] ?: listOf(), locales.searchMultiple(textFieldValue.value.text.lowercase(), limitLength = true, maxDistance = Int.MAX_VALUE) {
selectedLayout.value, listOf(
{ selectedLayout.value = it }, it.language, it.getDisplayName(systemLocale), it.getDisplayName(it)
{ Subtypes.getLayoutName(context, it) }, ).map { normalize(it.lowercase()) }.flatMap { it.split(" ") }.filter { it.isNotBlank() }
modifier = Modifier.width(180.dp) }
)
} }
Button(onClick = { LazyColumn {
Subtypes.addLanguage( item {
context, ScreenTitle("Select language to add", showBack = true, navController)
Subtypes.getLocale(selectedLocale.value), }
selectedLayout.value
item {
Surface(color = MaterialTheme.colorScheme.surfaceContainerHighest, shape = RoundedCornerShape(8.dp), modifier = Modifier.padding(8.dp).height(48.dp).fillMaxWidth()) {
Row(modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Default.Search, contentDescription = "Search")
Spacer(modifier = Modifier.width(8.dp))
BasicTextField(
value = textFieldValue.value,
onValueChange = { textFieldValue.value = it },
keyboardOptions = KeyboardOptions.Default.copy(
platformImeOptions = PlatformImeOptions(
privateImeOptions = "org.futo.inputmethod.latin.NoSuggestions=1,org.futo.inputmethod.latin.ForceLayout=qwerty,org.futo.inputmethod.latin.ForceLocale=zz"
)
),
modifier = Modifier.weight(1.0f),
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface),
textStyle = TextStyle.Default.copy(color = MaterialTheme.colorScheme.onSurface)
) )
navController.navigateUp() }
}, modifier = Modifier.fillMaxWidth().padding(16.dp)) { }
Text("Add") }
items(searchKeys) {
NavigationItem(
title = it.getDisplayName(systemLocale),
subtitle = it.getDisplayName(it),
style = NavigationItemStyle.MiscNoArrow,
navigate = {
navController.navigate("addLayout/" + it.toLanguageTag().urlEncode())
}
)
}
}
}
@Composable
fun LayoutPreview(name: String, locale: Locale, onClick: () -> Unit) {
val context = LocalContext.current
val layoutName = remember { LayoutManager.getLayout(context, name).name }
Box(
Modifier.padding(4.dp).fillMaxWidth().border(
1.dp,
MaterialTheme.colorScheme.outlineVariant,
RoundedCornerShape(8.dp)
).clickable { onClick() }) {
Column(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
KeyboardLayoutPreview(id = name, width = 172.dp, locale = locale)
Text(
layoutName,
style = TextSmallStyle,
modifier = Modifier.padding(4.dp),
textAlign = TextAlign.Center
)
}
}
}
@Preview
@Composable
fun SelectLayoutsScreen(navController: NavHostController = rememberNavController(), language: String = "en_US") {
val context = LocalContext.current
val locale = remember { Locale.forLanguageTag(language.replace("_", "-")) }
val layoutMapping = remember { LayoutManager.getLayoutMapping(context) }
val systemLocale = remember { context.resources.configuration.locales[0] }
val relevantLayouts = remember {
layoutMapping.entries.filter {
it.key.language == locale.language
}.flatMap { it.value }.toSet()
}
ScrollableList {
ScreenTitle(locale.getDisplayName(locale), showBack = true, navController)
ScreenTitle("Select a layout to add")
relevantLayouts.forEach {
LayoutPreview(it, locale) {
Subtypes.addLanguage(context, locale, it)
// Go back to languages
for(x in 0 until 3) navController.navigateUp()
navController.navigate("languages")
}
} }
} }
} }

View File

@ -6,13 +6,20 @@ import android.util.Log
import com.charleskorn.kaml.PolymorphismStyle import com.charleskorn.kaml.PolymorphismStyle
import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.Yaml
import com.charleskorn.kaml.YamlConfiguration import com.charleskorn.kaml.YamlConfiguration
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.EmptySerializersModule
import org.futo.inputmethod.latin.uix.actions.BugInfo import org.futo.inputmethod.latin.uix.actions.BugInfo
import org.futo.inputmethod.latin.uix.actions.BugViewerState import org.futo.inputmethod.latin.uix.actions.BugViewerState
import java.util.Locale import java.util.Locale
@Serializable
data class Mappings(
val languages: Map<String, List<String>>
)
object LayoutManager { object LayoutManager {
private var layoutsById: Map<String, Keyboard>? = null private var layoutsById: Map<String, Keyboard>? = null
private var localeToLayoutsMappings: Map<Locale, List<String>>? = null
private var initialized = false private var initialized = false
private fun listFilesRecursively(assetManager: AssetManager, path: String): List<String> { private fun listFilesRecursively(assetManager: AssetManager, path: String): List<String> {
@ -26,7 +33,7 @@ object LayoutManager {
private fun getAllLayoutPaths(assetManager: AssetManager): List<String> { private fun getAllLayoutPaths(assetManager: AssetManager): List<String> {
return listFilesRecursively(assetManager, "layouts").filter { return listFilesRecursively(assetManager, "layouts").filter {
it.endsWith(".yml") || it.endsWith(".yaml") (it.endsWith(".yml") || it.endsWith(".yaml")) && it != "mapping.yaml"
} }
} }
@ -35,6 +42,10 @@ object LayoutManager {
initialized = true initialized = true
localeToLayoutsMappings = parseMappings(context, "layouts/mapping.yaml").languages.mapKeys {
Locale.forLanguageTag(it.key.replace("_", "-"))
}
val assetManager = context.assets val assetManager = context.assets
val layoutPaths = getAllLayoutPaths(assetManager) val layoutPaths = getAllLayoutPaths(assetManager)
@ -65,10 +76,10 @@ object LayoutManager {
return layoutsById?.get(name) ?: throw IllegalArgumentException("Failed to find keyboard layout $name. Available layouts: ${layoutsById?.keys}") return layoutsById?.get(name) ?: throw IllegalArgumentException("Failed to find keyboard layout $name. Available layouts: ${layoutsById?.keys}")
} }
fun queryLayoutsForLocale(locale: Locale): List<Keyboard> { fun getLayoutMapping(context: Context): Map<Locale, List<String>> {
val language = locale.language init(context)
//val script = locale.getKeyboardScript()
return layoutsById!!.values.filter { it.languages.contains(language) } return localeToLayoutsMappings!!
} }
fun getAllLayoutNames(context: Context): List<String> { fun getAllLayoutNames(context: Context): List<String> {
@ -80,6 +91,20 @@ object LayoutManager {
} }
} }
private fun parseMappings(context: Context, mappingsPath: String): Mappings {
val yaml = Yaml(
EmptySerializersModule(),
YamlConfiguration(
polymorphismStyle = PolymorphismStyle.Property,
allowAnchorsAndAliases = true
)
)
return context.assets.open(mappingsPath).use { inputStream ->
val yamlString = inputStream.bufferedReader().use { it.readText() }
yaml.decodeFromString(Mappings.serializer(), yamlString)
}
}
private fun parseKeyboardYaml(context: Context, layoutPath: String): Keyboard { private fun parseKeyboardYaml(context: Context, layoutPath: String): Keyboard {
val yaml = Yaml( val yaml = Yaml(

View File

@ -1,6 +1,5 @@
package org.futo.inputmethod.v2keyboard package org.futo.inputmethod.v2keyboard
import org.futo.inputmethod.keyboard.internal.KeyboardLayoutKind
import org.futo.inputmethod.latin.common.Constants import org.futo.inputmethod.latin.common.Constants
fun getDefaultMoreKeysForKey(code: Int, relevantSpecShortcut: List<String>?): String { fun getDefaultMoreKeysForKey(code: Int, relevantSpecShortcut: List<String>?): String {