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:
parent
75f4fcb91a
commit
c2cb28668d
@ -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 -> {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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).
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user