diff --git a/app/src/main/java/helium314/keyboard/latin/utils/DictionaryUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/DictionaryUtils.kt
index 7a114b6c..c19e3b68 100644
--- a/app/src/main/java/helium314/keyboard/latin/utils/DictionaryUtils.kt
+++ b/app/src/main/java/helium314/keyboard/latin/utils/DictionaryUtils.kt
@@ -7,6 +7,12 @@ import android.text.method.LinkMovementMethod
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.core.content.edit
import helium314.keyboard.compat.locale
import helium314.keyboard.latin.R
@@ -14,6 +20,7 @@ import helium314.keyboard.latin.common.LocaleUtils
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
+import helium314.keyboard.settings.dialogs.ConfirmationDialog
import java.io.File
import java.util.Locale
@@ -97,6 +104,74 @@ fun createDictionaryTextHtml(message: String, locale: Locale, context: Context):
""".trimIndent()
}
+// why is this so horrible with annotated string?
+@Composable
+fun MissingDictionaryDialog(onDismissRequest: () -> Unit, locale: Locale) {
+ val prefs = LocalContext.current.prefs()
+ val availableDicts = createDictionaryTextAnnotated(locale)
+ val dictLink = "$DICTIONARY_URL/src/branch/main/dictionaries/main_$locale.dict"
+ val message = stringResource(R.string.no_dictionary_message, "§repl1§", locale.toString(), "§repl2§")
+ .replace("
", "\n") // compose doesn't understand html... // todo: modify the string?
+
+ // this relies on the order and thus is fragile, but so far it's fine with all translations
+ val part1 = message.substringBefore("§repl1§")
+ val part2 = message.substringBefore("§repl2§").substringAfter("§repl1§")
+ val part3 = message.substringAfter("§repl2§")
+
+ val annotatedString = buildAnnotatedString {
+ append(part1)
+ appendLink(stringResource(R.string.dictionary_link_text), DICTIONARY_URL)
+ append(part2)
+ appendLink(stringResource(R.string.dictionary_link_text), dictLink)
+ append(part3)
+ if (availableDicts.isNotEmpty()) {
+ appendLine()
+ appendLine()
+ append(availableDicts)
+ }
+ }
+
+ ConfirmationDialog(
+ onDismissRequest = onDismissRequest,
+ cancelButtonText = stringResource(R.string.dialog_close),
+ onConfirmed = { prefs.edit { putBoolean(Settings.PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG, true) } },
+ confirmButtonText = stringResource(R.string.no_dictionary_dont_show_again_button),
+ text = { Text(annotatedString) }
+ )
+}
+
+/** if dictionaries for [locale] or language are available returns links to them */
+@Composable
+fun createDictionaryTextAnnotated(locale: Locale): AnnotatedString {
+ val knownDicts = mutableListOf>()
+ val builder = AnnotatedString.Builder()
+ builder.appendLine(stringResource(R.string.dictionary_available))
+ val context = LocalContext.current
+ context.assets.open("dictionaries_in_dict_repo.csv").reader().forEachLine {
+ if (it.isBlank()) return@forEachLine
+ val (type, localeString, experimental) = it.split(",")
+ // we use a locale string here because that's in the dictionaries repo
+ // ideally the repo would switch to language tag, but not sure how this is handled in the dictionary header
+ // further, the dicts in the dictionaries repo should be compatible with other AOSP-based keyboards
+ val dictLocale = localeString.constructLocale()
+ if (LocaleUtils.getMatchLevel(locale, dictLocale) < LocaleUtils.LOCALE_GOOD_MATCH) return@forEachLine
+ val rawDictString = "$type: ${dictLocale.getDisplayName(context.resources.configuration.locale())}"
+ val dictString = if (experimental.isEmpty()) rawDictString
+ else context.getString(R.string.available_dictionary_experimental, rawDictString)
+ val dictBaseUrl = DICTIONARY_URL + DICTIONARY_DOWNLOAD_SUFFIX +
+ if (experimental.isEmpty()) DICTIONARY_NORMAL_SUFFIX else DICTIONARY_EXPERIMENTAL_SUFFIX
+ val dictLink = dictBaseUrl + type + "_" + localeString.lowercase() + ".dict"
+ knownDicts.add(dictString to dictLink)
+ }
+ if (knownDicts.isEmpty()) return AnnotatedString("")
+ knownDicts.forEach {
+ builder.append("\u2022 ") // bullet point as replacement for
+ builder.appendLink(it.first , it.second)
+ builder.appendLine()
+ }
+ return builder.toAnnotatedString()
+}
+
fun cleanUnusedMainDicts(context: Context) {
val dictionaryDir = File(DictionaryInfoUtils.getWordListCacheDirectory(context))
val dirs = dictionaryDir.listFiles() ?: return
diff --git a/app/src/main/java/helium314/keyboard/latin/utils/Ktx.kt b/app/src/main/java/helium314/keyboard/latin/utils/Ktx.kt
index d021ee6c..304ad837 100644
--- a/app/src/main/java/helium314/keyboard/latin/utils/Ktx.kt
+++ b/app/src/main/java/helium314/keyboard/latin/utils/Ktx.kt
@@ -8,6 +8,13 @@ import android.view.View
import android.widget.RelativeLayout
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextLinkStyles
+import androidx.compose.ui.text.withLink
import androidx.fragment.app.commit
import helium314.keyboard.latin.R
@@ -88,3 +95,13 @@ fun Context.prefs(): SharedPreferences = DeviceProtectedUtils.getSharedPreferenc
/** The "default" preferences that are only accessible after the device has been unlocked. */
fun Context.protectedPrefs(): SharedPreferences = getSharedPreferences("${packageName}_preferences", Context.MODE_PRIVATE)
+
+@Composable
+fun AnnotatedString.Builder.appendLink(text: String, url: String) =
+ withLink(
+ LinkAnnotation.Url(
+ url,
+ styles = TextLinkStyles(style = SpanStyle(color = MaterialTheme.colorScheme.primary))
+ )) {
+ append(text)
+ }
diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/DictionaryDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/DictionaryDialog.kt
index 496726ee..e309bb38 100644
--- a/app/src/main/java/helium314/keyboard/settings/dialogs/DictionaryDialog.kt
+++ b/app/src/main/java/helium314/keyboard/settings/dialogs/DictionaryDialog.kt
@@ -6,9 +6,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -22,11 +25,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
import helium314.keyboard.compat.locale
import helium314.keyboard.latin.Dictionary
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.LocaleUtils.localizedDisplayName
import helium314.keyboard.latin.utils.DictionaryInfoUtils
+import helium314.keyboard.latin.utils.createDictionaryTextAnnotated
import helium314.keyboard.settings.dictionaryFilePicker
import helium314.keyboard.settings.screens.getUserAndInternalDictionaries
import java.util.Locale
@@ -46,7 +51,8 @@ fun DictionaryDialog(
cancelButtonText = stringResource(R.string.dialog_close),
title = { Text(locale.localizedDisplayName(ctx)) },
text = {
- Column {
+ val state = rememberScrollState()
+ Column(Modifier.verticalScroll(state)) {
if (hasInternal) {
val color = if (dictionaries.none { it.startsWith(Dictionary.TYPE_MAIN + ":") }) MaterialTheme.colorScheme.onSurface
else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) // for disabled look
@@ -78,6 +84,11 @@ fun DictionaryDialog(
text = { Text(stringResource(R.string.remove_dictionary_message, type ?: ""))}
)
}
+ val dictString = createDictionaryTextAnnotated(locale)
+ if (dictString.isNotEmpty()) {
+ HorizontalDivider()
+ Text(dictString, style = LocalTextStyle.current.merge(lineHeight = 1.8.em))
+ }
}
},
neutralButtonText = stringResource(R.string.add_new_dictionary_title),
diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/SubtypeDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/SubtypeDialog.kt
index a94fc07b..bfd8ad2b 100644
--- a/app/src/main/java/helium314/keyboard/settings/dialogs/SubtypeDialog.kt
+++ b/app/src/main/java/helium314/keyboard/settings/dialogs/SubtypeDialog.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.dp
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
import helium314.keyboard.keyboard.internal.keyboard_parser.POPUP_KEYS_ALL
@@ -60,6 +61,7 @@ import helium314.keyboard.latin.utils.SettingsSubtype.Companion.toSettingsSubtyp
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.SubtypeSettings
import helium314.keyboard.latin.utils.SubtypeUtilsAdditional
+import helium314.keyboard.latin.utils.appendLink
import helium314.keyboard.latin.utils.getActivity
import helium314.keyboard.latin.utils.getDictionaryLocales
import helium314.keyboard.latin.utils.getSecondaryLocales
@@ -379,14 +381,19 @@ private fun MainLayoutRow(
)
}
if (showAddLayoutDialog) {
- // todo: maybe supply link to discussion section for layouts
- // todo: no html for compose, so message is broken
- // try annotatedString
- val link = "" + ctx.getString(R.string.dictionary_link_text) + ""
+ // layoutString contains "%s" since we didn't supply a formatArg
+ val layoutString = stringResource(R.string.message_add_custom_layout)
+ val linkText = stringResource(R.string.dictionary_link_text)
+ val annotated = buildAnnotatedString {
+ append(layoutString.substringBefore("%s"))
+ appendLink(linkText, LAYOUT_FORMAT_URL)
+ append(layoutString.substringAfter("%s"))
+ }
+
ConfirmationDialog(
onDismissRequest = { showAddLayoutDialog = false },
title = { Text(stringResource(R.string.button_title_add_custom_layout)) },
- text = { Text(stringResource(R.string.message_add_custom_layout, link)) },
+ text = { Text(annotated) },
onConfirmed = { showLayoutEditDialog = "new layout" to "" },
neutralButtonText = stringResource(R.string.button_load_custom),
onNeutral = {
diff --git a/app/src/main/java/helium314/keyboard/settings/screens/DictionaryScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/DictionaryScreen.kt
index 94f6fc0e..0a3cd9c3 100644
--- a/app/src/main/java/helium314/keyboard/settings/screens/DictionaryScreen.kt
+++ b/app/src/main/java/helium314/keyboard/settings/screens/DictionaryScreen.kt
@@ -16,6 +16,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.dp
import helium314.keyboard.latin.Dictionary
import helium314.keyboard.latin.R
@@ -28,6 +29,7 @@ import helium314.keyboard.latin.utils.DICTIONARY_URL
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.SubtypeSettings
+import helium314.keyboard.latin.utils.appendLink
import helium314.keyboard.latin.utils.getDictionaryLocales
import helium314.keyboard.latin.utils.locale
import helium314.keyboard.latin.utils.prefs
@@ -96,9 +98,14 @@ fun DictionaryScreen(
},
title = { Text(stringResource(R.string.add_new_dictionary_title)) },
text = {
- // todo: no html in compose
- val dictLink = "" + ctx.getString(R.string.dictionary_link_text) + ""
- Text(stringResource(R.string.add_dictionary, dictLink))
+ // addDictString contains "%s" since we didn't supply a formatArg
+ val addDictString = stringResource(R.string.add_dictionary)
+ val annotated = buildAnnotatedString {
+ append(addDictString.substringBefore("%s"))
+ appendLink(stringResource(R.string.dictionary_link_text), DICTIONARY_URL)
+ append(addDictString.substringAfter("%s"))
+ }
+ Text(annotated)
}
)
}
diff --git a/app/src/main/java/helium314/keyboard/settings/screens/LanguageScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/LanguageScreen.kt
index 30e8b02c..cafefc68 100644
--- a/app/src/main/java/helium314/keyboard/settings/screens/LanguageScreen.kt
+++ b/app/src/main/java/helium314/keyboard/settings/screens/LanguageScreen.kt
@@ -32,6 +32,7 @@ import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX
import helium314.keyboard.latin.utils.DictionaryInfoUtils
import helium314.keyboard.latin.utils.Log
+import helium314.keyboard.latin.utils.MissingDictionaryDialog
import helium314.keyboard.latin.utils.SettingsSubtype.Companion.toSettingsSubtype
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.SubtypeSettings
@@ -43,6 +44,7 @@ import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.SearchScreen
import helium314.keyboard.settings.SettingsActivity
import helium314.keyboard.settings.dialogs.SubtypeDialog
+import java.util.Locale
@Composable
fun LanguageScreen(
@@ -82,6 +84,7 @@ fun LanguageScreen(
.clickable { selectedSubtype = item }
.padding(vertical = 6.dp, horizontal = 16.dp)
) {
+ var showNoDictDialog by remember { mutableStateOf(false) }
Column(modifier = Modifier.weight(1f)) {
Text(item.displayName(ctx), style = MaterialTheme.typography.bodyLarge)
val description = item.getExtraValueOf(ExtraValue.SECONDARY_LOCALES)?.split(Separators.KV)
@@ -96,10 +99,14 @@ fun LanguageScreen(
Switch(
checked = item in enabledSubtypes,
onCheckedChange = {
+ if (it && !dictsAvailable(item.locale(), ctx))
+ showNoDictDialog = true
if (it) SubtypeSettings.addEnabledSubtype(prefs, item)
else SubtypeSettings.removeEnabledSubtype(ctx, item)
}
)
+ if (showNoDictDialog)
+ MissingDictionaryDialog({ showNoDictDialog = false }, item.locale())
}
}
)
@@ -118,6 +125,11 @@ fun LanguageScreen(
}
}
+private fun dictsAvailable(locale: Locale, context: Context): Boolean {
+ val (dicts, hasInternal) = getUserAndInternalDictionaries(context, locale)
+ return hasInternal || dicts.isNotEmpty()
+}
+
// sorting by display name is still slow, even with the cache... but probably good enough
private fun getSortedSubtypes(context: Context): List {
val systemLocales = SubtypeSettings.getSystemLocales()