0
0
mirror of https://github.com/florisboard/florisboard.git synced 2024-09-20 03:52:18 +02:00

Implement candidate auto-commit logic

This commit is contained in:
Patrick Goldinger 2022-07-04 19:30:39 +02:00
parent 75f4fcb91a
commit c2cb28668d
4 changed files with 55 additions and 26 deletions

View File

@ -17,6 +17,7 @@
package dev.patrickgold.florisboard.ime.keyboard
import android.content.Context
import android.icu.lang.UCharacter
import android.view.KeyEvent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@ -289,6 +290,9 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
}
fun commitCandidate(candidate: SuggestionCandidate) {
scope.launch {
candidate.sourceProvider?.notifySuggestionAccepted(subtypeManager.activeSubtype, candidate)
}
when (candidate) {
is ClipboardSuggestionCandidate -> editorInstance.commitClipboardItem(candidate.clipboardItem)
else -> editorInstance.commitCompletion(candidate.text.toString())
@ -504,6 +508,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
* enabled by the user.
*/
private fun handleSpace(data: KeyData) {
nlpManager.getAutoCommitCandidate()?.let { commitCandidate(it) }
if (prefs.keyboard.spaceBarSwitchesToCharacters.get()) {
when (activeState.keyboardMode) {
KeyboardMode.NUMERIC_ADVANCED,
@ -673,6 +678,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
KeyCode.VIEW_SYMBOLS2 -> activeState.keyboardMode = KeyboardMode.SYMBOLS2
else -> {
if (activeState.imeUiMode == ImeUiMode.MEDIA) {
nlpManager.getAutoCommitCandidate()?.let { commitCandidate(it) }
editorInstance.commitText(data.asString(isForDisplay = false))
return@batchEdit
}
@ -697,6 +703,9 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
else -> when (data.type) {
KeyType.CHARACTER, KeyType.NUMERIC ->{
val text = data.asString(isForDisplay = false)
if (!UCharacter.isUAlphabetic(UCharacter.codePointAt(text, 0))) {
nlpManager.getAutoCommitCandidate()?.let { commitCandidate(it) }
}
editorInstance.commitChar(text)
}
else -> {

View File

@ -194,6 +194,10 @@ class NlpManager(context: Context) {
}
}
fun getAutoCommitCandidate(): SuggestionCandidate? {
return activeCandidates.firstOrNull { it.isEligibleForAutoCommit }
}
fun getListOfWords(subtype: Subtype): List<String> {
return runBlocking { getSuggestionProvider(subtype).getListOfWords(subtype) }
}
@ -228,15 +232,7 @@ class NlpManager(context: Context) {
}
runBlocking {
internalSuggestionsGuard.withLock {
internalSuggestions.let { (_, suggestions) ->
suggestions.forEachIndexed { n, candidate ->
add(WordSuggestionCandidate(
text = candidate.text,
secondaryText = if (n % 2 == 1) "secondary" else null,
confidence = 0.5,
))
}
}
addAll(internalSuggestions.second)
}
}
}

View File

@ -145,9 +145,9 @@ interface SuggestionProvider : NlpProvider {
*
* @param subtype Information about the current subtype, primarily used for getting the primary and secondary
* language for correct dictionary selection.
* @param suggestion The exact suggestion candidate which has been accepted.
* @param candidate The exact suggestion candidate which has been accepted.
*/
suspend fun notifySuggestionAccepted(subtype: Subtype, suggestion: SuggestionCandidate)
suspend fun notifySuggestionAccepted(subtype: Subtype, candidate: SuggestionCandidate)
/**
* Is called when a previously automatically accepted suggestion has been reverted by the user with backspace. This
@ -155,9 +155,9 @@ interface SuggestionProvider : NlpProvider {
*
* @param subtype Information about the current subtype, primarily used for getting the primary and secondary
* language for correct dictionary selection.
* @param suggestion The exact suggestion candidate which has been reverted.
* @param candidate The exact suggestion candidate which has been reverted.
*/
suspend fun notifySuggestionReverted(subtype: Subtype, suggestion: SuggestionCandidate)
suspend fun notifySuggestionReverted(subtype: Subtype, candidate: SuggestionCandidate)
/**
* Called if the user requests to prevent a certain suggested word from showing again. It is up to the actual
@ -165,11 +165,11 @@ interface SuggestionProvider : NlpProvider {
*
* @param subtype Information about the current subtype, primarily used for getting the primary and secondary
* language for correct dictionary selection.
* @param suggestion The exact suggestion candidate which the user does not want to see again.
* @param candidate The exact suggestion candidate which the user does not want to see again.
*
* @return True if the removal request is supported and is accepted, false otherwise.
*/
suspend fun removeSuggestion(subtype: Subtype, suggestion: SuggestionCandidate): Boolean
suspend fun removeSuggestion(subtype: Subtype, candidate: SuggestionCandidate): Boolean
/**
* Interop method allowing the glide typing logic to perform its own magic.
@ -231,15 +231,15 @@ object FallbackNlpProvider : SpellingProvider, SuggestionProvider {
return emptyList()
}
override suspend fun notifySuggestionAccepted(subtype: Subtype, suggestion: SuggestionCandidate) {
override suspend fun notifySuggestionAccepted(subtype: Subtype, candidate: SuggestionCandidate) {
// Do nothing
}
override suspend fun notifySuggestionReverted(subtype: Subtype, suggestion: SuggestionCandidate) {
override suspend fun notifySuggestionReverted(subtype: Subtype, candidate: SuggestionCandidate) {
// Do nothing
}
override suspend fun removeSuggestion(subtype: Subtype, suggestion: SuggestionCandidate): Boolean {
override suspend fun removeSuggestion(subtype: Subtype, candidate: SuggestionCandidate): Boolean {
return false
}

View File

@ -26,6 +26,7 @@ import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
import dev.patrickgold.florisboard.ime.nlp.SuggestionProvider
import dev.patrickgold.florisboard.ime.nlp.WordSuggestionCandidate
import dev.patrickgold.florisboard.lib.android.readText
import dev.patrickgold.florisboard.lib.devtools.flogDebug
import dev.patrickgold.florisboard.lib.kotlin.guardedByLock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -48,15 +49,29 @@ class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProv
override val providerId = ProviderId
override suspend fun create() {
// Here we initialize our provider, set up a potential neural network, etc.
// Here we initialize our provider, set up all things which are not language dependent.
}
override suspend fun preload(subtype: Subtype) = withContext(Dispatchers.IO) {
// Here we have the chance to preload dictionaries and prepare a neural network for a specific language.
// Is kept in sync with the active keyboard subtype of the user, however a new preload does not necessary mean
// the previous language is not needed anymore (e.g. if the user constantly switches between two subtypes)
// To read a file from the APK assets the following methods can be used:
// appContext.assets.open()
// appContext.assets.reader()
// appContext.assets.bufferedReader()
// appContext.assets.readText()
// To copy an APK file/dir to the file system cache (appContext.cacheDir), the following methods are available:
// appContext.assets.copy()
// appContext.assets.copyRecursively()
// The subtype we get here contains a lot of data, however we are only interested in subtype.primaryLocale and
// subtype.secondaryLocales.
wordData.withLock { wordData ->
if (wordData.isEmpty()) {
// Here we use readText() because the test dictionary is a json dictionary
val rawData = appContext.assets.readText("ime/dict/data.json")
val jsonData = Json.decodeFromString(wordDataSerializer, rawData)
wordData.putAll(jsonData)
@ -74,8 +89,12 @@ class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProv
isPrivateSession: Boolean,
): SpellingResult {
return when (word.lowercase()) {
// Use typo for typing errors
"typo" -> SpellingResult.typo(arrayOf("typo1", "typo2", "typo3"))
// Use grammar error if the algorithm can detect this. On Android 11 and lower grammar errors are visually
// marked as typos due to a lack of support
"gerror" -> SpellingResult.grammarError(arrayOf("grammar1", "grammar2", "grammar3"))
// Use valid word for valid input
else -> SpellingResult.validWord()
}
}
@ -89,26 +108,31 @@ class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProv
): List<SuggestionCandidate> {
val word = content.composingText.ifBlank { "next" }
val suggestions = buildList {
for (n in 0..maxCandidateCount) {
for (n in 0 until maxCandidateCount) {
add(WordSuggestionCandidate(
text = "$word$n",
secondaryText = if (n % 2 == 1) "secondary" else null,
confidence = 0.5,
isEligibleForAutoCommit = n == 0 && word.startsWith("auto"),
// We set ourselves as the source provider so we can get notify events for our candidate
sourceProvider = this@LatinLanguageProvider,
))
}
}
return suggestions
}
override suspend fun notifySuggestionAccepted(subtype: Subtype, suggestion: SuggestionCandidate) {
// Ignore for now
override suspend fun notifySuggestionAccepted(subtype: Subtype, candidate: SuggestionCandidate) {
// We can use flogDebug, flogInfo, flogWarning and flogError for debug logging, which is a wrapper for Logcat
flogDebug { "notify accepted suggestion $candidate" }
}
override suspend fun notifySuggestionReverted(subtype: Subtype, suggestion: SuggestionCandidate) {
// Ignore for now
override suspend fun notifySuggestionReverted(subtype: Subtype, candidate: SuggestionCandidate) {
flogDebug { "notify reverted suggestion $candidate" }
}
override suspend fun removeSuggestion(subtype: Subtype, suggestion: SuggestionCandidate): Boolean {
override suspend fun removeSuggestion(subtype: Subtype, candidate: SuggestionCandidate): Boolean {
flogDebug { "remove suggestion request $candidate" }
return false
}
@ -122,6 +146,6 @@ class LatinLanguageProvider(context: Context) : SpellingProvider, SuggestionProv
override suspend fun destroy() {
// Here we have the chance to de-allocate memory and finish our work. However this might never be called if
// the app process is killed.
// the app process is killed (which will most likely always be the case).
}
}