Skip to content

Commit 09a229b

Browse files
committed
refactor: enhance inline suggestion
- Make InlineSuggestionsUi inside AlwaysUi - Improve the UI design - Add preference to enable or disable it
1 parent ab273da commit 09a229b

24 files changed

+342
-384
lines changed

app/src/main/java/com/osfans/trime/data/prefs/AppPrefs.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,13 @@ class AppPrefs(
9292
companion object {
9393
const val COMPOSING_TEXT_MODE = "composing_text_mode"
9494
const val ASCII_SWITCH_TIPS = "ascii_switch_tips"
95+
const val INLINE_SUGGESTIONS = "inline_suggestions"
9596
const val PREFERRED_VOICE_INPUT = "preferred_voice_input"
9697
}
9798

9899
val composingTextMode = enum(R.string.composing_text_mode, COMPOSING_TEXT_MODE, ComposingTextMode.DISABLE)
99100
val asciiSwitchTips = switch(R.string.ascii_switch_tips, ASCII_SWITCH_TIPS, true)
101+
val inlineSuggestions = switch(R.string.inline_suggestions, INLINE_SUGGESTIONS, true)
100102

101103
val preferredVoiceInput = list(
102104
R.string.preferred_voice_input,

app/src/main/java/com/osfans/trime/ime/bar/QuickBar.kt

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ package com.osfans.trime.ime.bar
77

88
import android.content.Context
99
import android.os.Build
10+
import android.util.Size
1011
import android.view.View
12+
import android.view.ViewGroup
1113
import android.view.inputmethod.EditorInfo
14+
import android.view.inputmethod.InlineSuggestion
15+
import android.view.inputmethod.InlineSuggestionsResponse
1216
import android.widget.ViewAnimator
17+
import android.widget.inline.InlineContentView
1318
import androidx.annotation.Keep
1419
import androidx.annotation.RequiresApi
1520
import androidx.lifecycle.lifecycleScope
@@ -23,10 +28,9 @@ import com.osfans.trime.data.theme.KeyActionManager
2328
import com.osfans.trime.data.theme.Theme
2429
import com.osfans.trime.ime.bar.ui.AlwaysUi
2530
import com.osfans.trime.ime.bar.ui.CandidateUi
26-
import com.osfans.trime.ime.bar.ui.InlineSuggestionUi
2731
import com.osfans.trime.ime.bar.ui.TabUi
2832
import com.osfans.trime.ime.broadcast.InputBroadcastReceiver
29-
import com.osfans.trime.ime.candidates.CandidateModule
33+
import com.osfans.trime.ime.candidates.compact.CompactCandidateModule
3034
import com.osfans.trime.ime.candidates.unrolled.window.FlexboxUnrolledCandidateWindow
3135
import com.osfans.trime.ime.core.TrimeInputMethodService
3236
import com.osfans.trime.ime.dependency.InputScope
@@ -39,13 +43,18 @@ import com.osfans.trime.ime.window.BoardWindowManager
3943
import com.osfans.trime.ui.main.ClipEditActivity
4044
import com.osfans.trime.util.AppUtils
4145
import kotlinx.coroutines.Job
46+
import kotlinx.coroutines.async
47+
import kotlinx.coroutines.awaitAll
4248
import kotlinx.coroutines.delay
4349
import kotlinx.coroutines.launch
4450
import me.tatarka.inject.annotations.Inject
4551
import splitties.dimensions.dp
4652
import splitties.views.dsl.core.add
4753
import splitties.views.dsl.core.lParams
4854
import splitties.views.dsl.core.matchParent
55+
import java.util.concurrent.Executor
56+
import kotlin.coroutines.resume
57+
import kotlin.coroutines.suspendCoroutine
4958

5059
@InputScope
5160
@Inject
@@ -55,7 +64,7 @@ class QuickBar(
5564
private val rime: RimeSession,
5665
private val theme: Theme,
5766
private val windowManager: BoardWindowManager,
58-
lazyCandidate: Lazy<CandidateModule>,
67+
lazyCandidate: Lazy<CompactCandidateModule>,
5968
lazyCommonKeyboardActionListener: Lazy<CommonKeyboardActionListener>,
6069
) : InputBroadcastReceiver {
6170

@@ -76,6 +85,7 @@ class QuickBar(
7685
private var clipboardTimeoutJob: Job? = null
7786

7887
private var isClipboardFresh: Boolean = false
88+
private var isInlineSuggestionPresent: Boolean = false
7989

8090
@Keep
8191
private val onClipboardUpdateListener = ClipboardHelper.OnClipboardUpdateListener {
@@ -107,6 +117,7 @@ class QuickBar(
107117
val newState =
108118
when {
109119
isClipboardFresh -> AlwaysUi.State.Clipboard
120+
isInlineSuggestionPresent -> AlwaysUi.State.InlineSuggestion
110121
else -> AlwaysUi.State.Toolbar
111122
}
112123
if (newState == alwaysUi.currentState) return
@@ -148,17 +159,13 @@ class QuickBar(
148159
}
149160

150161
private val candidateUi by lazy {
151-
CandidateUi(context, candidate.compactCandidateModule.view).apply {
162+
CandidateUi(context, candidate.view).apply {
152163
unrollButton.apply {
153164
onSwipeListener = swipeDownHideKeyboardCallback
154165
}
155166
}
156167
}
157168

158-
private val inlineSuggestionUi by lazy {
159-
InlineSuggestionUi(context, candidate.suggestionCandidateModule.view)
160-
}
161-
162169
private val tabUi by lazy {
163170
TabUi(context, theme)
164171
}
@@ -188,7 +195,7 @@ class QuickBar(
188195
private fun setUnrollButtonToAttach() {
189196
candidateUi.unrollButton.setOnClickListener { view ->
190197
windowManager.attachWindow(
191-
FlexboxUnrolledCandidateWindow(context, service, rime, theme, this, windowManager, candidate.compactCandidateModule),
198+
FlexboxUnrolledCandidateWindow(context, service, rime, theme, this, windowManager, candidate),
192199
)
193200
}
194201
candidateUi.unrollButton.setIcon(R.drawable.ic_baseline_expand_more_24)
@@ -241,7 +248,6 @@ class QuickBar(
241248
add(alwaysUi.root, lParams(matchParent, matchParent))
242249
add(candidateUi.root, lParams(matchParent, matchParent))
243250
add(tabUi.root, lParams(matchParent, matchParent))
244-
add(inlineSuggestionUi.root, lParams(matchParent, matchParent))
245251

246252
evalAlwaysUiState()
247253
ClipboardHelper.addOnUpdateListener(onClipboardUpdateListener)
@@ -266,12 +272,59 @@ class QuickBar(
266272
barStateMachine.push(QuickBarStateMachine.TransitionEvent.WindowDetached)
267273
}
268274

275+
private val suggestionSize by lazy {
276+
Size(ViewGroup.LayoutParams.WRAP_CONTENT, context.dp(themedHeight))
277+
}
278+
279+
private val directExecutor by lazy {
280+
Executor { it.run() }
281+
}
282+
269283
@RequiresApi(Build.VERSION_CODES.R)
270-
fun handleInlineSuggestions(isEmpty: Boolean) {
271-
barStateMachine.push(
272-
QuickBarStateMachine.TransitionEvent.SuggestionUpdated,
273-
QuickBarStateMachine.BooleanKey.SuggestionEmpty to isEmpty,
274-
)
284+
fun handleInlineSuggestions(response: InlineSuggestionsResponse): Boolean {
285+
val suggestions = response.inlineSuggestions
286+
if (suggestions.isEmpty()) {
287+
isInlineSuggestionPresent = false
288+
return true
289+
}
290+
var pinned: InlineSuggestion? = null
291+
val scrollable = mutableListOf<InlineSuggestion>()
292+
var extraPinnedCount = 0
293+
suggestions.forEach {
294+
if (it.info.isPinned) {
295+
if (pinned == null) {
296+
pinned = it
297+
} else {
298+
scrollable.add(extraPinnedCount++, it)
299+
}
300+
} else {
301+
scrollable.add(it)
302+
}
303+
}
304+
service.lifecycleScope.launch {
305+
alwaysUi.inlineSuggestionsUi.setPinnedView(
306+
pinned?.let { inflateInlineContentView(it) },
307+
)
308+
}
309+
service.lifecycleScope.launch {
310+
val views = scrollable.map { s ->
311+
service.lifecycleScope.async {
312+
inflateInlineContentView(s)
313+
}
314+
}.awaitAll()
315+
alwaysUi.inlineSuggestionsUi.setScrollableViews(views)
316+
}
317+
isInlineSuggestionPresent = true
318+
evalAlwaysUiState()
319+
return true
320+
}
321+
322+
@RequiresApi(Build.VERSION_CODES.R)
323+
private suspend fun inflateInlineContentView(suggestion: InlineSuggestion): InlineContentView? = suspendCoroutine { c ->
324+
// callback view might be null
325+
suggestion.inflate(context, suggestionSize, directExecutor) { v ->
326+
c.resume(v)
327+
}
275328
}
276329

277330
override fun onRimeOptionUpdated(value: RimeMessage.OptionMessage.Data) {

app/src/main/java/com/osfans/trime/ime/bar/QuickBarStateMachine.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
package com.osfans.trime.ime.bar
77

88
import com.osfans.trime.ime.bar.QuickBarStateMachine.BooleanKey.CandidateEmpty
9-
import com.osfans.trime.ime.bar.QuickBarStateMachine.BooleanKey.SuggestionEmpty
109
import com.osfans.trime.ime.bar.QuickBarStateMachine.State.Always
1110
import com.osfans.trime.ime.bar.QuickBarStateMachine.State.Candidate
12-
import com.osfans.trime.ime.bar.QuickBarStateMachine.State.Suggestion
1311
import com.osfans.trime.ime.bar.QuickBarStateMachine.State.Tab
1412
import com.osfans.trime.util.BuildTransitionEvent
1513
import com.osfans.trime.util.EventStateMachine
@@ -20,12 +18,10 @@ object QuickBarStateMachine {
2018
Always,
2119
Candidate,
2220
Tab,
23-
Suggestion,
2421
}
2522

2623
enum class BooleanKey : EventStateMachine.BooleanStateKey {
2724
CandidateEmpty,
28-
SuggestionEmpty,
2925
}
3026

3127
enum class TransitionEvent(
@@ -34,31 +30,23 @@ object QuickBarStateMachine {
3430
CandidatesUpdated({
3531
from(Always) transitTo Candidate on (CandidateEmpty to false)
3632
from(Candidate) transitTo Always on (CandidateEmpty to true)
37-
from(Suggestion) transitTo Candidate on (CandidateEmpty to false)
3833
}),
3934
BarBoardWindowAttached({
4035
from(Always) transitTo Tab
4136
from(Candidate) transitTo Tab
42-
from(Suggestion) transitTo Tab
4337
}),
4438
WindowDetached({
4539
// candidate state has higher priority so here it goes first
4640
from(Tab) transitTo Candidate on (CandidateEmpty to false)
47-
from(Tab) transitTo Suggestion on (SuggestionEmpty to false)
4841
from(Tab) transitTo Always
4942
}),
50-
SuggestionUpdated({
51-
from(Always) transitTo Suggestion on (SuggestionEmpty to false)
52-
from(Suggestion) transitTo Always on (SuggestionEmpty to true)
53-
}),
5443
}
5544

5645
fun new(block: (State) -> Unit) = EventStateMachine<State, TransitionEvent, BooleanKey>(
5746
initialState = Always,
5847
externalBooleanStates =
5948
mutableMapOf(
6049
CandidateEmpty to true,
61-
SuggestionEmpty to true,
6250
),
6351
).apply {
6452
onNewStateListener = block

app/src/main/java/com/osfans/trime/ime/bar/ui/AlwaysUi.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class AlwaysUi(
3535
enum class State {
3636
Toolbar,
3737
Clipboard,
38+
InlineSuggestion,
3839
}
3940

4041
var currentState = State.Toolbar
@@ -61,10 +62,13 @@ class AlwaysUi(
6162

6263
val clipboardUi = ClipboardSuggestionUi(ctx)
6364

65+
val inlineSuggestionsUi = InlineSuggestionsUi(ctx)
66+
6467
private val animator =
6568
ViewAnimator(ctx).apply {
6669
add(buttonsUi.root, lParams(matchParent, matchParent))
6770
add(clipboardUi.root, lParams(matchParent, matchParent))
71+
add(inlineSuggestionsUi.root, lParams(matchParent, matchParent))
6872
}
6973

7074
override val root: ConstraintLayout = constraintLayout {
@@ -112,6 +116,7 @@ class AlwaysUi(
112116
when (state) {
113117
State.Toolbar -> animator.displayedChild = 0
114118
State.Clipboard -> animator.displayedChild = 1
119+
State.InlineSuggestion -> animator.displayedChild = 2
115120
}
116121
currentState = state
117122
}

app/src/main/java/com/osfans/trime/ime/bar/ui/InlineSuggestionUi.kt

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)