mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add initial inline suggestions support
This commit is contained in:
parent
9d4ea1f7c1
commit
bd0368d89f
@ -129,6 +129,7 @@ dependencies {
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
|
||||
implementation 'androidx.datastore:datastore-preferences:1.0.0'
|
||||
implementation 'androidx.autofill:autofill:1.1.0'
|
||||
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||
|
@ -115,7 +115,8 @@
|
||||
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:settingsActivity="org.futo.inputmethod.latin.settings.SettingsActivity"
|
||||
android:isDefault="@bool/im_is_default"
|
||||
android:supportsSwitchingToNextInputMethod="true">
|
||||
android:supportsSwitchingToNextInputMethod="true"
|
||||
android:supportsInlineSuggestions="true">
|
||||
<subtype android:icon="@drawable/ic_ime_switcher_dark"
|
||||
android:label="@string/subtype_en_US"
|
||||
android:subtypeId="0xc9194f98"
|
||||
|
@ -2,11 +2,17 @@ package org.futo.inputmethod.latin
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.CompletionInfo
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.view.inputmethod.InlineSuggestionsRequest
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -54,6 +60,7 @@ import org.futo.inputmethod.latin.uix.DynamicThemeProvider
|
||||
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
|
||||
import org.futo.inputmethod.latin.uix.KeyboardManagerForAction
|
||||
import org.futo.inputmethod.latin.uix.THEME_KEY
|
||||
import org.futo.inputmethod.latin.uix.createInlineSuggestionsRequest
|
||||
import org.futo.inputmethod.latin.uix.deferGetSetting
|
||||
import org.futo.inputmethod.latin.uix.deferSetSetting
|
||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
||||
@ -113,6 +120,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
return currWindowAction != null
|
||||
}
|
||||
|
||||
private var inlineSuggestions: List<InlineSuggestion> = listOf()
|
||||
|
||||
private fun recreateKeyboard() {
|
||||
legacyInputView = latinIMELegacy.onCreateInputView()
|
||||
latinIMELegacy.loadKeyboard()
|
||||
@ -224,13 +233,20 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
@Composable
|
||||
private fun MainKeyboardViewWithActionBar() {
|
||||
Column {
|
||||
if (shouldShowSuggestionStrip) {
|
||||
// Don't show suggested words when it's not meant to be shown
|
||||
val suggestedWordsOrNull = if(shouldShowSuggestionStrip) {
|
||||
suggestedWords
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
ActionBar(
|
||||
suggestedWords,
|
||||
suggestedWordsOrNull,
|
||||
latinIMELegacy,
|
||||
inlineSuggestions = inlineSuggestions,
|
||||
onActionActivated = { onActionActivated(it) }
|
||||
)
|
||||
}
|
||||
|
||||
LegacyKeyboardView()
|
||||
}
|
||||
}
|
||||
@ -506,4 +522,17 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
|
||||
deferSetSetting(THEME_KEY, newTheme.key)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest {
|
||||
return createInlineSuggestionsRequest(this, this.activeColorScheme)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
|
||||
inlineSuggestions = response.inlineSuggestions
|
||||
setContent()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
package org.futo.inputmethod.latin.uix
|
||||
|
||||
import android.os.Build
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@ -14,9 +13,9 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.Icon
|
||||
@ -24,12 +23,9 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@ -41,7 +37,6 @@ import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
@ -64,7 +59,6 @@ import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.latin.SuggestedWords
|
||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo
|
||||
import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo.KIND_TYPED
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
||||
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||
@ -359,6 +353,7 @@ fun ActionBar(
|
||||
words: SuggestedWords?,
|
||||
suggestionStripListener: SuggestionStripView.Listener,
|
||||
onActionActivated: (Action) -> Unit,
|
||||
inlineSuggestions: List<InlineSuggestion>,
|
||||
forceOpenActionsInitially: Boolean = false,
|
||||
) {
|
||||
val isActionsOpen = remember { mutableStateOf(forceOpenActionsInitially) }
|
||||
@ -372,6 +367,8 @@ fun ActionBar(
|
||||
|
||||
if(isActionsOpen.value) {
|
||||
ActionItems(onActionActivated)
|
||||
} else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
InlineSuggestions(inlineSuggestions)
|
||||
} else if(words != null) {
|
||||
SuggestionItems(words) {
|
||||
suggestionStripListener.pickSuggestionManually(
|
||||
@ -440,8 +437,9 @@ fun PreviewActionBarWithSuggestions(colorScheme: ColorScheme = DarkColorScheme)
|
||||
UixThemeWrapper(colorScheme) {
|
||||
ActionBar(
|
||||
words = exampleSuggestedWords,
|
||||
suggestionStripListener = ExampleListener(),
|
||||
onActionActivated = { },
|
||||
suggestionStripListener = ExampleListener()
|
||||
inlineSuggestions = listOf()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -452,8 +450,9 @@ fun PreviewActionBarWithEmptySuggestions(colorScheme: ColorScheme = DarkColorSch
|
||||
UixThemeWrapper(colorScheme) {
|
||||
ActionBar(
|
||||
words = exampleSuggestedWordsEmpty,
|
||||
suggestionStripListener = ExampleListener(),
|
||||
onActionActivated = { },
|
||||
suggestionStripListener = ExampleListener()
|
||||
inlineSuggestions = listOf()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -464,8 +463,9 @@ fun PreviewExpandedActionBar(colorScheme: ColorScheme = DarkColorScheme) {
|
||||
UixThemeWrapper(colorScheme) {
|
||||
ActionBar(
|
||||
words = exampleSuggestedWordsEmpty,
|
||||
onActionActivated = { },
|
||||
suggestionStripListener = ExampleListener(),
|
||||
onActionActivated = { },
|
||||
inlineSuggestions = listOf(),
|
||||
forceOpenActionsInitially = true
|
||||
)
|
||||
}
|
||||
|
154
java/src/org/futo/inputmethod/latin/uix/InlineSuggestionView.kt
Normal file
154
java/src/org/futo/inputmethod/latin/uix/InlineSuggestionView.kt
Normal file
@ -0,0 +1,154 @@
|
||||
package org.futo.inputmethod.latin.uix
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Size
|
||||
import android.util.TypedValue
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.view.inputmethod.InlineSuggestionsRequest
|
||||
import android.widget.inline.InlineContentView
|
||||
import android.widget.inline.InlinePresentationSpec
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.autofill.inline.UiVersions
|
||||
import androidx.autofill.inline.common.ImageViewStyle
|
||||
import androidx.autofill.inline.common.TextViewStyle
|
||||
import androidx.autofill.inline.common.ViewStyle
|
||||
import androidx.autofill.inline.v1.InlineSuggestionUi
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun createInlineSuggestionsRequest(
|
||||
context: Context,
|
||||
activeColorScheme: ColorScheme
|
||||
): InlineSuggestionsRequest {
|
||||
val fromDp = { v: Float ->
|
||||
TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
v,
|
||||
context.resources.displayMetrics
|
||||
).roundToInt()
|
||||
}
|
||||
|
||||
val stylesBuilder = UiVersions.newStylesBuilder()
|
||||
val suggestionStyle = InlineSuggestionUi.newStyleBuilder()
|
||||
.setSingleIconChipStyle(
|
||||
ViewStyle.Builder()
|
||||
.setBackgroundColor(activeColorScheme.secondaryContainer.toArgb())
|
||||
.setPadding(0, 0, 0, 0)
|
||||
.build()
|
||||
)
|
||||
.setChipStyle(
|
||||
ViewStyle.Builder()
|
||||
.setBackgroundColor(activeColorScheme.secondaryContainer.toArgb())
|
||||
.setPadding(
|
||||
fromDp(8.0f),
|
||||
fromDp(0.0f),
|
||||
fromDp(8.0f),
|
||||
fromDp(0.0f),
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.setStartIconStyle(ImageViewStyle.Builder().setLayoutMargin(0, 0, 0, 0).build())
|
||||
.setTitleStyle(
|
||||
TextViewStyle.Builder()
|
||||
.setLayoutMargin(
|
||||
fromDp(4.0f),
|
||||
fromDp(0.0f),
|
||||
fromDp(4.0f),
|
||||
fromDp(0.0f),
|
||||
)
|
||||
.setTextColor(activeColorScheme.onSecondaryContainer.toArgb())
|
||||
.setTextSize(14.0f)
|
||||
.build()
|
||||
)
|
||||
.setSubtitleStyle(
|
||||
TextViewStyle.Builder()
|
||||
.setLayoutMargin(
|
||||
fromDp(4.0f),
|
||||
fromDp(0.0f),
|
||||
fromDp(4.0f),
|
||||
fromDp(0.0f),
|
||||
)
|
||||
.setTextColor(activeColorScheme.onSecondaryContainer.copy(alpha = 0.5f).toArgb())
|
||||
.setTextSize(12.0f)
|
||||
.build()
|
||||
)
|
||||
.setEndIconStyle(
|
||||
ImageViewStyle.Builder()
|
||||
.setLayoutMargin(0, 0, 0, 0)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
stylesBuilder.addStyle(suggestionStyle)
|
||||
|
||||
val stylesBundle = stylesBuilder.build()
|
||||
|
||||
val spec = InlinePresentationSpec.Builder(
|
||||
Size(0, 0),
|
||||
Size(Int.MAX_VALUE, Int.MAX_VALUE)
|
||||
).setStyle(stylesBundle).build()
|
||||
|
||||
return InlineSuggestionsRequest.Builder(listOf(spec)).let { request ->
|
||||
request.setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
|
||||
request.build()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@Composable
|
||||
fun InlineSuggestionView(inlineSuggestion: InlineSuggestion) = with(LocalDensity.current) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val size = Size(ViewGroup.LayoutParams.WRAP_CONTENT, 32.dp.toPx().toInt())
|
||||
var inlineContentView by remember { mutableStateOf<InlineContentView?>(null) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
inlineSuggestion.inflate(context, size, context.mainExecutor) { inflatedView ->
|
||||
if (inflatedView != null) {
|
||||
inlineContentView = inflatedView
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
if (inlineContentView != null) {
|
||||
AndroidView(
|
||||
factory = { inlineContentView!! },
|
||||
modifier = Modifier.padding(4.dp, 0.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
@Composable
|
||||
fun RowScope.InlineSuggestions(suggestions: List<InlineSuggestion>) {
|
||||
LazyRow(modifier = Modifier.weight(1.0f).padding(0.dp, 4.dp)) {
|
||||
items(suggestions.size) {
|
||||
InlineSuggestionView(suggestions[it])
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user