Skip to content

Commit fd07d25

Browse files
committed
feat: request and handle inline suggestion in TrimeInputMethodService
Add `InlineSuggestionHandler` to create request for inline suggestion and handle the responded suggestion.
1 parent 0b5e14a commit fd07d25

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2015 - 2025 Rime community
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
package com.osfans.trime.ime.candidates.suggestion
7+
8+
import android.annotation.SuppressLint
9+
import android.content.Context
10+
import android.content.res.ColorStateList
11+
import android.graphics.Color
12+
import android.graphics.drawable.Icon
13+
import android.os.Build
14+
import android.util.Size
15+
import android.view.View
16+
import android.view.ViewGroup
17+
import android.view.inputmethod.InlineSuggestionsRequest
18+
import android.view.inputmethod.InlineSuggestionsResponse
19+
import android.widget.inline.InlinePresentationSpec
20+
import androidx.annotation.RequiresApi
21+
import androidx.autofill.inline.UiVersions
22+
import androidx.autofill.inline.common.ImageViewStyle
23+
import androidx.autofill.inline.common.TextViewStyle
24+
import androidx.autofill.inline.common.ViewStyle
25+
import androidx.autofill.inline.v1.InlineSuggestionUi
26+
import androidx.core.graphics.ColorUtils
27+
import com.osfans.trime.R
28+
import com.osfans.trime.data.theme.ColorManager
29+
import splitties.dimensions.dp
30+
import java.util.concurrent.Executor
31+
import kotlin.coroutines.resume
32+
import kotlin.coroutines.suspendCoroutine
33+
34+
class InlineSuggestionHandler(
35+
private val context: Context,
36+
) {
37+
@SuppressLint("NewApi", "RestrictedApi")
38+
fun createRequest(): InlineSuggestionsRequest {
39+
val firstTextColor = ColorManager.getColor("candidate_text_color")!!
40+
val backColor = ColorManager.getColor("candidate_background")!!
41+
42+
val style =
43+
InlineSuggestionUi
44+
.newStyleBuilder()
45+
.setSingleIconChipStyle(
46+
ViewStyle
47+
.Builder()
48+
.setBackgroundColor(Color.TRANSPARENT)
49+
.setPadding(0, 0, 0, 0)
50+
.build(),
51+
).setChipStyle(
52+
ViewStyle
53+
.Builder()
54+
.setBackground(
55+
Icon.createWithResource(context, R.drawable.bg_inline_suggestion).apply {
56+
setTint(ColorUtils.blendARGB(backColor, firstTextColor, 0.2f))
57+
},
58+
).build(),
59+
).setTitleStyle(
60+
TextViewStyle
61+
.Builder()
62+
.setLayoutMargin(context.dp(4), 0, context.dp(4), 0)
63+
.setTextColor(firstTextColor)
64+
.setTextSize(14f)
65+
.build(),
66+
).setSubtitleStyle(
67+
TextViewStyle
68+
.Builder()
69+
.setTextColor(
70+
ColorUtils.blendARGB(firstTextColor, backColor, 0.3f),
71+
).setTextSize(12f)
72+
.build(),
73+
).setStartIconStyle(
74+
ImageViewStyle
75+
.Builder()
76+
.setTintList(ColorStateList.valueOf(firstTextColor))
77+
.build(),
78+
).setEndIconStyle(
79+
ImageViewStyle
80+
.Builder()
81+
.setTintList(ColorStateList.valueOf(firstTextColor))
82+
.build(),
83+
).build()
84+
val styleBundle =
85+
UiVersions
86+
.newStylesBuilder()
87+
.addStyle(style)
88+
.build()
89+
val spec =
90+
InlinePresentationSpec
91+
.Builder(Size(0, 0), Size(context.dp(160), Int.MAX_VALUE))
92+
.setStyle(styleBundle)
93+
.build()
94+
return InlineSuggestionsRequest
95+
.Builder(listOf(spec))
96+
.setMaxSuggestionCount(InlineSuggestionsRequest.SUGGESTION_COUNT_UNLIMITED)
97+
.build()
98+
}
99+
100+
private val suggestionSize by lazy {
101+
Size(ViewGroup.LayoutParams.WRAP_CONTENT, context.dp(INLINE_SUGGESTION_HEIGHT))
102+
}
103+
104+
@RequiresApi(Build.VERSION_CODES.R)
105+
suspend fun inflateSuggestion(response: InlineSuggestionsResponse): List<View> =
106+
response.inlineSuggestions.map {
107+
suspendCoroutine { c ->
108+
it.inflate(context, suggestionSize, directExecutor) { v ->
109+
c.resume(v)
110+
}
111+
}
112+
}
113+
114+
companion object {
115+
private const val INLINE_SUGGESTION_HEIGHT = 40
116+
117+
private val directExecutor by lazy {
118+
Executor { it.run() }
119+
}
120+
}
121+
}

app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt

+20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import android.content.Intent
1313
import android.content.res.Configuration
1414
import android.inputmethodservice.InputMethodService
1515
import android.os.Build
16+
import android.os.Bundle
1617
import android.os.SystemClock
1718
import android.text.InputType
1819
import android.view.InputDevice
@@ -25,6 +26,8 @@ import android.view.WindowManager
2526
import android.view.inputmethod.CursorAnchorInfo
2627
import android.view.inputmethod.EditorInfo
2728
import android.view.inputmethod.ExtractedTextRequest
29+
import android.view.inputmethod.InlineSuggestionsRequest
30+
import android.view.inputmethod.InlineSuggestionsResponse
2831
import android.widget.FrameLayout
2932
import androidx.annotation.Keep
3033
import androidx.core.content.ContextCompat
@@ -48,6 +51,7 @@ import com.osfans.trime.data.theme.ColorManager
4851
import com.osfans.trime.data.theme.Theme
4952
import com.osfans.trime.data.theme.ThemeManager
5053
import com.osfans.trime.ime.broadcast.IntentReceiver
54+
import com.osfans.trime.ime.candidates.suggestion.InlineSuggestionHandler
5155
import com.osfans.trime.ime.composition.ComposingPopupWindow
5256
import com.osfans.trime.ime.keyboard.InitializationUi
5357
import com.osfans.trime.ime.keyboard.InputFeedbackManager
@@ -87,6 +91,8 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
8791
private var mIntentReceiver: IntentReceiver? = null
8892
private val locales = Array(2) { Locale.getDefault() }
8993

94+
private lateinit var inlineSuggestionHandler: InlineSuggestionHandler
95+
9096
var lastCommittedText: CharSequence = ""
9197
private set
9298

@@ -204,6 +210,7 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
204210
2 -> Locale(latinLocale[0], latinLocale[1])
205211
else -> Locale.US
206212
}
213+
inlineSuggestionHandler = InlineSuggestionHandler(this@TrimeInputMethodService)
207214
Timber.d("Trime.onCreate completed")
208215
}
209216
} catch (e: Exception) {
@@ -438,6 +445,19 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
438445
}
439446
}
440447

448+
@SuppressLint("NewApi", "RestrictedApi")
449+
override fun onCreateInlineSuggestionsRequest(uiExtras: Bundle): InlineSuggestionsRequest? = inlineSuggestionHandler.createRequest()
450+
451+
@SuppressLint("NewApi")
452+
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
453+
lifecycleScope.launch {
454+
val views = inlineSuggestionHandler.inflateSuggestion(response)
455+
456+
inputView?.updateInlineSuggestion(views)
457+
}
458+
return true
459+
}
460+
441461
override fun onStartInputView(
442462
attribute: EditorInfo,
443463
restarting: Boolean,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ SPDX-FileCopyrightText: 2015 - 2025 Rime community
3+
~ SPDX-License-Identifier: GPL-3.0-or-later
4+
-->
5+
6+
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
7+
android:color="#FFFFFFFF">
8+
<item
9+
android:bottom="4dp"
10+
android:left="4dp"
11+
android:right="4dp"
12+
android:shape="rectangle"
13+
android:top="4dp">
14+
<shape>
15+
<corners android:radius="6dp" />
16+
<solid android:color="#FFFFFF" />
17+
<stroke
18+
android:width="1dp"
19+
android:color="#FFFFFF" />
20+
</shape>
21+
</item>
22+
</ripple>

0 commit comments

Comments
 (0)