Skip to content

Commit

Permalink
feat: request and handle inline suggestion in TrimeInputMethodService
Browse files Browse the repository at this point in the history
Add `InlineSuggestionHandler` to create request for inline suggestion and handle
the responded suggestion.
  • Loading branch information
goofyz committed Jan 14, 2025
1 parent 71d9568 commit a491151
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2015 - 2025 Rime community
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.osfans.trime.ime.candidates.suggestion

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Icon
import android.os.Build
import android.util.Size
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InlineSuggestionsRequest
import android.view.inputmethod.InlineSuggestionsResponse
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.core.graphics.ColorUtils
import com.osfans.trime.R
import com.osfans.trime.data.theme.ColorManager
import splitties.dimensions.dp
import java.util.concurrent.Executor
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

class InlineSuggestionHandler(
private val context: Context,
) {
@SuppressLint("NewApi", "RestrictedApi")
fun createRequest(): InlineSuggestionsRequest {
val firstTextColor = ColorManager.getColor("candidate_text_color")!!
val backColor = ColorManager.getColor("candidate_background")!!

val style =
InlineSuggestionUi
.newStyleBuilder()
.setSingleIconChipStyle(
ViewStyle
.Builder()
.setBackgroundColor(Color.TRANSPARENT)
.setPadding(0, 0, 0, 0)
.build(),
).setChipStyle(
ViewStyle
.Builder()
.setBackground(
Icon.createWithResource(context, R.drawable.bg_inline_suggestion).apply {
setTint(ColorUtils.blendARGB(backColor, firstTextColor, 0.2f))
},
).build(),
).setTitleStyle(
TextViewStyle
.Builder()
.setLayoutMargin(context.dp(4), 0, context.dp(4), 0)
.setTextColor(firstTextColor)
.setTextSize(14f)
.build(),
).setSubtitleStyle(
TextViewStyle
.Builder()
.setTextColor(
ColorUtils.blendARGB(firstTextColor, backColor, 0.3f),
).setTextSize(12f)
.build(),
).setStartIconStyle(
ImageViewStyle
.Builder()
.setTintList(ColorStateList.valueOf(firstTextColor))
.build(),
).setEndIconStyle(
ImageViewStyle
.Builder()
.setTintList(ColorStateList.valueOf(firstTextColor))
.build(),
).build()
val styleBundle =
UiVersions
.newStylesBuilder()
.addStyle(style)
.build()
val spec =
InlinePresentationSpec
.Builder(Size(0, 0), Size(context.dp(160), Int.MAX_VALUE))
.setStyle(styleBundle)
.build()
return InlineSuggestionsRequest
.Builder(listOf(spec))
.setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
.build()
}

private val suggestionSize by lazy {
Size(ViewGroup.LayoutParams.WRAP_CONTENT, context.dp(INLINE_SUGGESTION_HEIGHT))
}

@RequiresApi(Build.VERSION_CODES.R)
suspend fun inflateSuggestion(response: InlineSuggestionsResponse): List<View> =
response.inlineSuggestions.map {
suspendCoroutine { c ->
it.inflate(context, suggestionSize, directExecutor) { v ->
c.resume(v)
}
}
}

companion object {
private const val INLINE_SUGGESTION_HEIGHT = 40

private val directExecutor by lazy {
Executor { it.run() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.content.Intent
import android.content.res.Configuration
import android.inputmethodservice.InputMethodService
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.text.InputType
import android.view.InputDevice
Expand All @@ -25,6 +26,8 @@ import android.view.WindowManager
import android.view.inputmethod.CursorAnchorInfo
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.ExtractedTextRequest
import android.view.inputmethod.InlineSuggestionsRequest
import android.view.inputmethod.InlineSuggestionsResponse
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.core.content.ContextCompat
Expand All @@ -48,6 +51,7 @@ import com.osfans.trime.data.theme.ColorManager
import com.osfans.trime.data.theme.Theme
import com.osfans.trime.data.theme.ThemeManager
import com.osfans.trime.ime.broadcast.IntentReceiver
import com.osfans.trime.ime.candidates.suggestion.InlineSuggestionHandler
import com.osfans.trime.ime.composition.ComposingPopupWindow
import com.osfans.trime.ime.keyboard.InitializationUi
import com.osfans.trime.ime.keyboard.InputFeedbackManager
Expand Down Expand Up @@ -87,6 +91,8 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
private var mIntentReceiver: IntentReceiver? = null
private val locales = Array(2) { Locale.getDefault() }

private lateinit var inlineSuggestionHandler: InlineSuggestionHandler

var lastCommittedText: CharSequence = ""
private set

Expand Down Expand Up @@ -204,6 +210,7 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
2 -> Locale(latinLocale[0], latinLocale[1])
else -> Locale.US
}
inlineSuggestionHandler = InlineSuggestionHandler(this@TrimeInputMethodService)
Timber.d("Trime.onCreate completed")
}
} catch (e: Exception) {
Expand Down Expand Up @@ -438,6 +445,19 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
}
}

@SuppressLint("NewApi", "RestrictedApi")
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? = inlineSuggestionHandler.createRequest()

@SuppressLint("NewApi")
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
lifecycleScope.launch {
val views = inlineSuggestionHandler.inflateSuggestion(response)

inputView?.updateInlineSuggestion(views)
}
return true
}

override fun onStartInputView(
attribute: EditorInfo,
restarting: Boolean,
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/res/drawable/bg_inline_suggestion.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2015 - 2025 Rime community
~ SPDX-License-Identifier: GPL-3.0-or-later
-->

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#FFFFFFFF">
<item
android:bottom="4dp"
android:left="4dp"
android:right="4dp"
android:shape="rectangle"
android:top="4dp">
<shape>
<corners android:radius="6dp" />
<solid android:color="#FFFFFF" />
<stroke
android:width="1dp"
android:color="#FFFFFF" />
</shape>
</item>
</ripple>

0 comments on commit a491151

Please sign in to comment.