Update suggestions bar to show verbatim word only when relevant and make it clear it's the verbatim word

This commit is contained in:
Aleksandras Kostarevas 2024-09-28 01:06:07 +03:00
parent 2f8d847186
commit ccbd59ee80
3 changed files with 134 additions and 83 deletions

View File

@ -15,6 +15,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.LifecycleCoroutineScope
import org.futo.inputmethod.latin.LatinIME
import org.futo.inputmethod.latin.SuggestionBlacklist
import org.futo.inputmethod.latin.uix.theme.ThemeOption
import org.futo.inputmethod.v2keyboard.KeyboardSizingCalculator
import java.util.Locale
@ -67,6 +68,7 @@ interface KeyboardManagerForAction {
fun activateAction(action: Action)
fun showActionEditor()
fun getSuggestionBlacklist(): SuggestionBlacklist
fun getLatinIMEForDebug(): LatinIME
fun isDeviceLocked(): Boolean

View File

@ -72,6 +72,7 @@ import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextOverflow
@ -87,6 +88,7 @@ import org.futo.inputmethod.latin.SuggestedWords
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_EMOJI_SUGGESTION
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
import org.futo.inputmethod.latin.SuggestionBlacklist
import org.futo.inputmethod.latin.common.Constants
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewListener
import org.futo.inputmethod.latin.uix.actions.FavoriteActions
@ -98,7 +100,6 @@ import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
import java.lang.Integer.min
import kotlin.math.ceil
import kotlin.math.roundToInt
@ -220,17 +221,9 @@ fun AutoFitText(
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean, onClick: () -> Unit, onLongClick: () -> Unit) {
val word = try {
words.getWord(idx)
} catch(e: IndexOutOfBoundsException) {
null
}
val wordInfo = try {
words.getInfo(idx)
} catch(e: IndexOutOfBoundsException) {
null
}
val wordInfo = words.getInfoOrNull(idx)
val isVerbatim = wordInfo?.kind == KIND_TYPED
val word = wordInfo?.mWord
val actualIsPrimary = isPrimary && (words.mWillAutoCorrect || ((wordInfo?.isExactMatch) == true))
@ -277,9 +270,12 @@ fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean,
) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
if (word != null) {
AutoFitText(word, style = textStyle, modifier = textModifier
.align(Center)
.padding(2.dp))
val modifier = textModifier.align(Center).padding(2.dp)
if(isVerbatim) {
AutoFitText('"' + word + '"', style = textStyle.copy(fontStyle = FontStyle.Italic), modifier = modifier)
} else {
AutoFitText(word, style = textStyle, modifier = modifier)
}
}
}
}
@ -296,87 +292,137 @@ fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, isPrimary: Boolean,
}
data class SuggestionLayout(
/** Set to the word to be autocorrected to */
val autocorrectMatch: SuggestedWordInfo?,
// Show the most probable in the middle, then left, then right
val ORDER_OF_SUGGESTIONS = listOf(1, 0, 2)
/** Other words, sorted by likelihood */
val sortedMatches: List<SuggestedWordInfo>,
/** Emoji suggestions if they are to be shown */
val emojiMatches: List<SuggestedWordInfo>,
/** The exact word the user typed */
val verbatimWord: SuggestedWordInfo?,
/** Set to true if the best match is so unlikely that we should show verbatim instead */
val areSuggestionsClueless: Boolean,
/** Set to true if this is a gesture update, and we should only show one suggestion */
val isGestureBatch: Boolean,
val presentableSuggestions: List<SuggestedWordInfo>
)
fun SuggestedWords.getInfoOrNull(idx: Int): SuggestedWordInfo? = try {
getInfo(idx)
} catch(e: IndexOutOfBoundsException) {
null
}
fun makeSuggestionLayout(words: SuggestedWords, blacklist: SuggestionBlacklist): SuggestionLayout {
val typedWord = words.getInfoOrNull(SuggestedWords.INDEX_OF_TYPED_WORD)?.let {
if(it.kind == KIND_TYPED) { it } else { null }
}?.let {
if(blacklist.isSuggestedWordOk(it)) {
it
} else {
null
}
}
val autocorrectMatch = words.getInfoOrNull(SuggestedWords.INDEX_OF_AUTO_CORRECTION)?.let {
if(words.mWillAutoCorrect) { it } else { null }
}
// We actually have to avoid sorting these because they are provided sorted in an important order
val emojiMatches = words.mSuggestedWordInfoList.filter {
it.kind == KIND_EMOJI_SUGGESTION
}
val sortedMatches = words.mSuggestedWordInfoList.filter {
it != typedWord && it.kind != KIND_TYPED && it != autocorrectMatch && !emojiMatches.contains(it)
}
val areSuggestionsClueless = (autocorrectMatch ?: sortedMatches.getOrNull(0))?.let {
it.mOriginatesFromTransformerLM && it.mScore < -50
} ?: false
val isGestureBatch = words.mInputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH
val presentableSuggestions = (
listOf(
typedWord,
autocorrectMatch,
) + sortedMatches
).filterNotNull()
return SuggestionLayout(
autocorrectMatch = autocorrectMatch,
sortedMatches = sortedMatches,
emojiMatches = emojiMatches,
verbatimWord = typedWord,
areSuggestionsClueless = areSuggestionsClueless,
isGestureBatch = isGestureBatch,
presentableSuggestions = presentableSuggestions
)
}
@Composable
fun RowScope.SuggestionItems(words: SuggestedWords, onClick: (i: Int) -> Unit, onLongClick: (i: Int) -> Unit) {
val maxSuggestions = min(ORDER_OF_SUGGESTIONS.size, words.size())
val layout = makeSuggestionLayout(words, LocalManager.current.getSuggestionBlacklist())
if(maxSuggestions == 0) {
Spacer(modifier = Modifier.weight(1.0f))
return
}
if(maxSuggestions == 1 || words.mInputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH) {
SuggestionItem(
words,
0,
isPrimary = true,
onClick = { onClick(0) },
onLongClick = { onLongClick(0) }
)
return
} else if(words.mInputStyle == SuggestedWords.INPUT_STYLE_TAIL_BATCH && maxSuggestions > 1) {
//words.mSuggestedWordInfoList.removeAt(0);
}
var offset = 0
try {
val info = words.getInfo(0)
if (info.kind == KIND_TYPED && !info.isExactMatch && !info.isExactMatchWithIntentionalOmission) {
offset = 1
val suggestionItem = @Composable { suggestion: SuggestedWordInfo? ->
if(suggestion != null) {
val idx = words.indexOf(suggestion)
SuggestionItem(
words,
idx,
isPrimary = idx == SuggestedWords.INDEX_OF_AUTO_CORRECTION,
onClick = { onClick(idx) },
onLongClick = { onLongClick(idx) }
)
} else {
Spacer(Modifier.weight(1.0f))
}
} catch(_: IndexOutOfBoundsException) {
}
// Check for "clueless" suggestions, and display typed word in center if so
try {
if(offset == 1) {
val info = words.getInfo(1)
if(info.mOriginatesFromTransformerLM && info.mScore < -50) {
offset = 0;
println(layout)
when {
layout.isGestureBatch ||
layout.presentableSuggestions.size <= 1 -> suggestionItem(layout.presentableSuggestions.firstOrNull())
layout.autocorrectMatch != null -> {
var supplementalSuggestionIndex = 0
if(layout.emojiMatches.isEmpty()) {
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex++))
} else {
suggestionItem(layout.emojiMatches[0])
}
SuggestionSeparator()
suggestionItem(layout.autocorrectMatch)
SuggestionSeparator()
if(layout.verbatimWord != null && layout.verbatimWord.mWord != layout.autocorrectMatch.mWord) {
suggestionItem(layout.verbatimWord)
} else {
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex))
}
}
} catch(_: IndexOutOfBoundsException) {
}
val suggestionOrder = mutableListOf(
ORDER_OF_SUGGESTIONS[0] + offset,
ORDER_OF_SUGGESTIONS[1] + offset,
if(offset == 1) { 0 - offset } else { ORDER_OF_SUGGESTIONS[2] } + offset,
)
// Find emoji
try {
for(i in 0 until words.size()) {
val info = words.getInfo(i)
if(info.mKindAndFlags == KIND_EMOJI_SUGGESTION && i > 2) {
suggestionOrder[0] = i
else -> {
var supplementalSuggestionIndex = 1
if(layout.emojiMatches.isEmpty()) {
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex++))
} else {
suggestionItem(layout.emojiMatches[0])
}
SuggestionSeparator()
suggestionItem(layout.sortedMatches.getOrNull(0))
SuggestionSeparator()
suggestionItem(layout.sortedMatches.getOrNull(supplementalSuggestionIndex))
}
} catch(_: IndexOutOfBoundsException) {
}
for (i in 0 until maxSuggestions) {
SuggestionItem(
words,
suggestionOrder[i],
isPrimary = i == (maxSuggestions / 2),
onClick = { onClick(suggestionOrder[i]) },
onLongClick = { onLongClick(suggestionOrder[i]) }
)
if (i < maxSuggestions - 1) SuggestionSeparator()
}
}

View File

@ -94,6 +94,7 @@ import org.futo.inputmethod.latin.LatinIME
import org.futo.inputmethod.latin.R
import org.futo.inputmethod.latin.SuggestedWords
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
import org.futo.inputmethod.latin.SuggestionBlacklist
import org.futo.inputmethod.latin.common.Constants
import org.futo.inputmethod.latin.inputlogic.InputLogic
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewListener
@ -327,6 +328,8 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
latinIME.sizingCalculator
override fun getLatinIMEForDebug(): LatinIME = latinIME
override fun getSuggestionBlacklist(): SuggestionBlacklist = latinIME.suggestionBlacklist
}
data class ActiveDialogRequest(