diff --git a/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt b/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt index 0fd881e24..39a604a17 100644 --- a/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt +++ b/app/src/main/java/be/scri/helpers/data/AutocompletionDataManager.kt @@ -2,34 +2,59 @@ package be.scri.helpers.data import be.scri.helpers.DatabaseFileManager +import be.scri.helpers.StringUtils.isWordCapitalized /** * This class manages the autocomplete system. * It loads words from a language-specific SQLite database, * and stores them in a Trie data structure for fast prefix-based lookup. + * If the `autocomplete_lexicon` table/Trie is not available, it falls back to caching all noun words. */ class AutocompletionDataManager( private val fileManager: DatabaseFileManager, ) { private val trie = Trie() + private var trieLoaded = false + private val nounWords = mutableListOf() /** * Loads all words from the language-specific database into the trie. + * If the `autocomplete_lexicon` table/Trie is not present, it loads noun words from the specified columns instead. * * @param language The language code (e.g. "en", "id") for which to load words. + * @param numbersColumns Column names from the contract's `numbers` map. */ - fun loadWords(language: String) { + fun loadWords( + language: String, + numbersColumns: List = emptyList(), + ) { val db = fileManager.getLanguageDatabase(language) ?: return db.use { database -> - if (!database.tableExists("autocomplete_lexicon")) return - - database.rawQuery("SELECT word FROM autocomplete_lexicon", null).use { cursor -> - val wordIndex = cursor.getColumnIndex("word") - while (cursor.moveToNext()) { - val word = cursor.getString(wordIndex)?.lowercase()?.trim() - if (!word.isNullOrEmpty()) { - trie.insert(word) + if (database.tableExists("autocomplete_lexicon")) { + database.rawQuery("SELECT word FROM autocomplete_lexicon", null).use { cursor -> + val wordIndex = cursor.getColumnIndex("word") + while (cursor.moveToNext()) { + val word = cursor.getString(wordIndex)?.lowercase()?.trim() + if (!word.isNullOrEmpty()) { + trie.insert(word) + } + } + } + trieLoaded = true + } else if (database.tableExists("nouns") && numbersColumns.isNotEmpty()) { + val unionQuery = + numbersColumns.joinToString(" UNION ") { column -> + "SELECT DISTINCT $column AS word FROM nouns WHERE $column IS NOT NULL AND $column != ''" + } + " ORDER BY word ASC" + + database.rawQuery(unionQuery, null).use { cursor -> + val wordIndex = cursor.getColumnIndex("word") + while (cursor.moveToNext()) { + val word = cursor.getString(wordIndex)?.lowercase()?.trim() + if (!word.isNullOrEmpty()) { + nounWords.add(word) + } } } } @@ -38,6 +63,7 @@ class AutocompletionDataManager( /** * Returns autocomplete suggestions for a given prefix. + * Uses the Trie if loaded, otherwise filters the cached noun word list. * * @param prefix The starting text to search for (e.g. "ap"). * @param limit The maximum number of suggestions to return (default: 3). @@ -47,5 +73,33 @@ class AutocompletionDataManager( fun getAutocompletions( prefix: String, limit: Int = 3, - ): List = trie.searchPrefix(prefix, limit) + ): List { + val isCapitalized = isWordCapitalized(prefix) + + val results = + if (trieLoaded) { + trie.searchPrefix(prefix.lowercase().trim(), limit) + } else { + getAutocompletionsFromNouns(prefix.lowercase().trim(), limit) + } + return if (isCapitalized) { + results.map { it.replaceFirstChar { it.uppercaseChar() } } + } else { + results + } + } + + /** + * Filters the cached noun word list to find matches that start with the given prefix. + */ + private fun getAutocompletionsFromNouns( + prefix: String, + limit: Int, + ): List { + if (nounWords.isEmpty() || prefix.isBlank()) return emptyList() + val normalizedPrefix = prefix.lowercase().trim() + return nounWords + .filter { it.startsWith(normalizedPrefix) } + .take(limit) + } } diff --git a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt index 60bad4b9a..50b791af4 100644 --- a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt @@ -493,7 +493,11 @@ abstract class GeneralKeyboardIME( ?.toSet() nounKeywords = dbManagers.genderManager.findGenderOfWord(languageAlias, dataContract) suggestionWords = dbManagers.suggestionManager.getSuggestions(languageAlias) - autocompletionManager.loadWords(languageAlias) + val numbersColumns = + dataContract?.numbers?.let { map -> + (map.keys + map.values).distinct() + } ?: emptyList() + autocompletionManager.loadWords(languageAlias, numbersColumns) caseAnnotation = dbManagers.prepositionManager.getCaseAnnotations(languageAlias) val tempConjugateOutput = dbManagers.conjugateDataManager.getTheConjugateLabels(languageAlias, dataContract, "describe")