Skip to content

Commit 083d519

Browse files
committed
feat: allow user to determine the candidates view mode
1 parent 8a0fea7 commit 083d519

File tree

20 files changed

+158
-79
lines changed

20 files changed

+158
-79
lines changed

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.content.SharedPreferences
99
import androidx.preference.PreferenceManager
1010
import com.osfans.trime.R
1111
import com.osfans.trime.data.base.DataManager
12+
import com.osfans.trime.ime.candidates.popup.PopupCandidatesMode
1213
import com.osfans.trime.ime.enums.FullscreenMode
1314
import com.osfans.trime.ime.enums.InlinePreeditMode
1415
import com.osfans.trime.util.appContext
@@ -22,13 +23,28 @@ class AppPrefs(
2223
) {
2324
private val applicationContext: WeakReference<Context> = WeakReference(appContext)
2425

26+
private val providers = mutableListOf<PreferenceDelegateProvider>()
27+
28+
fun <T : PreferenceDelegateProvider> registerProvider(providerF: (SharedPreferences) -> T): T {
29+
val provider = providerF(shared)
30+
providers.add(provider)
31+
return provider
32+
}
33+
34+
private fun <T : PreferenceDelegateProvider> T.register() =
35+
this.apply {
36+
registerProvider { this }
37+
}
38+
2539
val internal = Internal(shared)
2640
val keyboard = Keyboard(shared)
2741
val theme = Theme(shared)
2842
val profile = Profile(shared)
2943
val clipboard = Clipboard(shared)
3044
val other = Other(shared)
3145

46+
val candidates = Candidates(shared).register()
47+
3248
companion object {
3349
private var defaultInstance: AppPrefs? = null
3450

@@ -84,7 +100,6 @@ class AppPrefs(
84100
companion object {
85101
const val INLINE_PREEDIT_MODE = "keyboard__inline_preedit"
86102
const val SOFT_CURSOR_ENABLED = "keyboard__soft_cursor"
87-
const val FLOATING_WINDOW_ENABLED = "keyboard__show_window"
88103
const val POPUP_KEY_PRESS_ENABLED = "keyboard__show_key_popup"
89104
const val SWITCHES_ENABLED = "keyboard__show_switches"
90105
const val LANDSCAPE_MODE = "keyboard__landscape_mode"
@@ -122,7 +137,6 @@ class AppPrefs(
122137
var inlinePreedit by enum(INLINE_PREEDIT_MODE, InlinePreeditMode.PREVIEW)
123138
var fullscreenMode by enum(FULLSCREEN_MODE, FullscreenMode.AUTO_SHOW)
124139
val softCursorEnabled by bool(SOFT_CURSOR_ENABLED, true)
125-
val popupWindowEnabled by bool(FLOATING_WINDOW_ENABLED, true)
126140
val popupKeyPressEnabled by bool(POPUP_KEY_PRESS_ENABLED, false)
127141
val switchesEnabled by bool(SWITCHES_ENABLED, true)
128142
val switchArrowEnabled by bool(SWITCH_ARROW_ENABLED, true)
@@ -161,6 +175,16 @@ class AppPrefs(
161175
var isSpeakCommit by bool(SPEAK_COMMIT_ENABLED, false)
162176
}
163177

178+
class Candidates(
179+
shared: SharedPreferences,
180+
) : PreferenceDelegateOwner(shared, R.string.candidates_window) {
181+
companion object {
182+
const val MODE = "candidates__mode"
183+
}
184+
185+
val mode = enum(R.string.candidates_mode, MODE, PopupCandidatesMode.PREEDIT_ONLY)
186+
}
187+
164188
/**
165189
* Wrapper class of theme and color settings.
166190
*/

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.osfans.trime.ime.bar.ui.CandidateUi
2424
import com.osfans.trime.ime.bar.ui.TabUi
2525
import com.osfans.trime.ime.broadcast.InputBroadcastReceiver
2626
import com.osfans.trime.ime.candidates.CompactCandidateModule
27+
import com.osfans.trime.ime.candidates.popup.PopupCandidatesMode
2728
import com.osfans.trime.ime.candidates.unrolled.window.FlexboxUnrolledCandidateWindow
2829
import com.osfans.trime.ime.core.TrimeInputMethodService
2930
import com.osfans.trime.ime.dependency.InputScope
@@ -51,6 +52,7 @@ class QuickBar(
5152
private val prefs = AppPrefs.defaultInstance()
5253

5354
private val showSwitchers get() = prefs.keyboard.switchesEnabled
55+
private val candidatesMode by prefs.candidates.mode
5456

5557
val themedHeight =
5658
theme.generalStyle.candidateViewHeight + theme.generalStyle.commentHeight
@@ -145,7 +147,8 @@ class QuickBar(
145147
override fun onInputContextUpdate(ctx: RimeProto.Context) {
146148
barStateMachine.push(
147149
QuickBarStateMachine.TransitionEvent.CandidatesUpdated,
148-
QuickBarStateMachine.BooleanKey.CandidateEmpty to ctx.menu.candidates.isEmpty(),
150+
QuickBarStateMachine.BooleanKey.CandidateEmpty to
151+
(ctx.menu.candidates.isEmpty() || candidatesMode == PopupCandidatesMode.CURRENT_PAGE),
149152
)
150153
}
151154

app/src/main/java/com/osfans/trime/ime/candidates/CompactCandidateModule.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ import androidx.lifecycle.lifecycleScope
1717
import androidx.recyclerview.widget.RecyclerView
1818
import com.google.android.flexbox.FlexboxLayoutManager
1919
import com.osfans.trime.R
20+
import com.osfans.trime.core.CandidateItem
21+
import com.osfans.trime.core.RimeProto
2022
import com.osfans.trime.daemon.RimeSession
2123
import com.osfans.trime.daemon.launchOnReady
24+
import com.osfans.trime.data.prefs.AppPrefs
2225
import com.osfans.trime.data.theme.ColorManager
2326
import com.osfans.trime.data.theme.Theme
2427
import com.osfans.trime.ime.bar.QuickBar
2528
import com.osfans.trime.ime.bar.UnrollButtonStateMachine
2629
import com.osfans.trime.ime.broadcast.InputBroadcastReceiver
2730
import com.osfans.trime.ime.candidates.adapter.CompactCandidateViewAdapter
31+
import com.osfans.trime.ime.candidates.popup.PopupCandidatesMode
2832
import com.osfans.trime.ime.candidates.unrolled.decoration.FlexboxVerticalDecoration
2933
import com.osfans.trime.ime.core.TrimeInputMethodService
3034
import com.osfans.trime.ime.dependency.InputScope
@@ -48,6 +52,8 @@ class CompactCandidateModule(
4852
val theme: Theme,
4953
val bar: QuickBar,
5054
) : InputBroadcastReceiver {
55+
private val candidatesMode by AppPrefs.defaultInstance().candidates.mode
56+
5157
private val _unrolledCandidateOffset =
5258
MutableSharedFlow<Int>(
5359
replay = 1,
@@ -58,7 +64,7 @@ class CompactCandidateModule(
5864

5965
fun refreshUnrolled() {
6066
runBlocking {
61-
_unrolledCandidateOffset.emit(adapter.before + view.childCount)
67+
_unrolledCandidateOffset.emit(adapter.previous + view.childCount)
6268
}
6369
bar.unrollButtonStateMachine.push(
6470
UnrollButtonStateMachine.TransitionEvent.UnrolledCandidatesUpdated,
@@ -70,10 +76,10 @@ class CompactCandidateModule(
7076
val adapter by lazy {
7177
CompactCandidateViewAdapter(theme).apply {
7278
setOnItemClickListener { _, _, position ->
73-
rime.launchOnReady { it.selectCandidate(before + position) }
79+
rime.launchOnReady { it.selectCandidate(previous + position) }
7480
}
7581
setOnItemLongClickListener { _, view, position ->
76-
showCandidateAction(before + position, items[position].text, view)
82+
showCandidateAction(previous + position, items[position].text, view)
7783
true
7884
}
7985
}
@@ -110,6 +116,18 @@ class CompactCandidateModule(
110116
}
111117
}
112118

119+
override fun onInputContextUpdate(ctx: RimeProto.Context) {
120+
if (candidatesMode != PopupCandidatesMode.PREEDIT_ONLY) return
121+
val candidates = ctx.menu.candidates.map { CandidateItem(it.comment ?: "", it.text) }
122+
val isLastPage = ctx.menu.isLastPage
123+
val previous = ctx.menu.run { pageSize * pageNumber }
124+
val highlightedIdx = ctx.menu.highlightedCandidateIndex
125+
adapter.updateCandidates(candidates, isLastPage, previous, highlightedIdx)
126+
if (candidates.isEmpty()) {
127+
refreshUnrolled()
128+
}
129+
}
130+
113131
private var candidateActionMenu: PopupMenu? = null
114132

115133
fun showCandidateAction(

app/src/main/java/com/osfans/trime/ime/candidates/adapter/CompactCandidateViewAdapter.kt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import splitties.views.setPaddingDp
2121
open class CompactCandidateViewAdapter(
2222
val theme: Theme,
2323
) : BaseQuickAdapter<CandidateItem, CandidateViewHolder>() {
24-
var sticky: Int = 0
25-
private set
26-
2724
var isLastPage: Boolean = false
2825
private set
2926

@@ -33,21 +30,16 @@ open class CompactCandidateViewAdapter(
3330
var highlightedIdx: Int = -1
3431
private set
3532

36-
val before: Int
37-
get() = sticky + previous
38-
3933
fun updateCandidates(
4034
list: List<CandidateItem>,
4135
isLastPage: Boolean,
4236
previous: Int,
4337
highlightedIdx: Int,
44-
sticky: Int = 0,
4538
) {
4639
this.isLastPage = isLastPage
4740
this.previous = previous
48-
this.sticky = sticky
4941
this.highlightedIdx = highlightedIdx
50-
super.submitList(list.drop(sticky))
42+
super.submitList(list)
5143
}
5244

5345
override fun onCreateViewHolder(
@@ -71,15 +63,14 @@ open class CompactCandidateViewAdapter(
7163
item: CandidateItem?,
7264
) {
7365
val (comment, text) = item!!
74-
val idx = sticky + position
7566
holder.ui.run {
7667
label.text = text
7768
altLabel.text = comment
78-
highlight(theme.generalStyle.candidateUseCursor && idx == highlightedIdx)
69+
highlight(theme.generalStyle.candidateUseCursor && position == highlightedIdx)
7970
}
8071
holder.text = text
8172
holder.comment = comment
82-
holder.idx = before + position // unused
73+
holder.idx = previous + position // unused
8374
holder.ui.root.updateLayoutParams<FlexboxLayoutManager.LayoutParams> {
8475
minWidth = 0
8576
flexGrow = 0f

app/src/main/java/com/osfans/trime/ime/candidates/popup/PagedCandidatesUi.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package com.osfans.trime.ime.candidates.popup
77

88
import android.content.Context
9+
import android.view.View
910
import android.view.ViewGroup
1011
import androidx.recyclerview.widget.RecyclerView
1112
import com.chad.library.adapter4.BaseQuickAdapter
@@ -72,6 +73,8 @@ class PagedCandidatesUi(
7273

7374
override val root =
7475
recyclerView {
76+
visibility = View.GONE
77+
7578
isFocusable = false
7679
adapter = candidatesAdapter
7780
layoutManager = candidatesLayoutManager
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2015 - 2024 Rime community
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
package com.osfans.trime.ime.candidates.popup
7+
8+
import com.osfans.trime.R
9+
import com.osfans.trime.data.prefs.PreferenceDelegateEnum
10+
11+
enum class PopupCandidatesMode(
12+
override val stringRes: Int,
13+
) : PreferenceDelegateEnum {
14+
CURRENT_PAGE(R.string.current_page_of_candidates),
15+
PREEDIT_ONLY(R.string.preedit_only),
16+
}

app/src/main/java/com/osfans/trime/ime/candidates/unrolled/window/BaseUnrolledCandidateWindow.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ abstract class BaseUnrolledCandidateWindow(
124124

125125
private fun updateCandidatesWithOffset(offset: Int) {
126126
val candidates = compactCandidate.adapter.items
127-
val sticky = compactCandidate.adapter.sticky
128-
if (candidates.isEmpty() && sticky == 0) {
127+
if (candidates.isEmpty()) {
129128
windowManager.attachWindow(KeyboardWindow)
130129
} else {
131130
adapter.refreshWithOffset(offset)
@@ -139,7 +138,7 @@ abstract class BaseUnrolledCandidateWindow(
139138
bar.unrollButtonStateMachine.push(
140139
UnrollButtonStateMachine.TransitionEvent.UnrolledCandidatesDetached,
141140
UnrollButtonStateMachine.BooleanKey.UnrolledCandidatesEmpty to
142-
(compactCandidate.adapter.run { isLastPage && (before + itemCount) == adapter.offset }),
141+
(compactCandidate.adapter.run { isLastPage && (previous + itemCount) == adapter.offset }),
143142
)
144143
offsetJob?.cancel()
145144
candidatesSubmitJob?.cancel()

app/src/main/java/com/osfans/trime/ime/composition/CandidatesView.kt

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
1313
import com.osfans.trime.core.RimeProto
1414
import com.osfans.trime.daemon.RimeSession
1515
import com.osfans.trime.daemon.launchOnReady
16+
import com.osfans.trime.data.prefs.AppPrefs
1617
import com.osfans.trime.data.theme.Theme
1718
import com.osfans.trime.ime.candidates.popup.PagedCandidatesUi
19+
import com.osfans.trime.ime.candidates.popup.PopupCandidatesMode
1820
import splitties.dimensions.dp
1921
import splitties.views.dsl.constraintlayout.below
2022
import splitties.views.dsl.constraintlayout.bottomOfParent
@@ -32,6 +34,8 @@ class CandidatesView(
3234
val rime: RimeSession,
3335
val theme: Theme,
3436
) : ConstraintLayout(ctx) {
37+
private val candidatesMode by AppPrefs.defaultInstance().candidates.mode
38+
3539
private var menu = RimeProto.Context.Menu()
3640
private var inputComposition = RimeProto.Context.Composition()
3741

@@ -62,18 +66,29 @@ class CandidatesView(
6266
private fun updateUi() {
6367
if (evaluateVisibility()) {
6468
preeditUi.update(inputComposition)
65-
preeditUi.root.visibility = if (preeditUi.visible) View.VISIBLE else View.GONE
66-
candidatesUi.update(menu)
67-
// visibility = View.VISIBLE
68-
} else {
69-
// visibility = View.GONE
69+
when (candidatesMode) {
70+
PopupCandidatesMode.CURRENT_PAGE -> {
71+
candidatesUi.root.let {
72+
if (it.visibility == View.GONE) {
73+
it.visibility = View.VISIBLE
74+
}
75+
}
76+
candidatesUi.update(menu)
77+
}
78+
79+
PopupCandidatesMode.PREEDIT_ONLY -> {
80+
candidatesUi.root.let {
81+
if (it.visibility != View.GONE) {
82+
it.visibility = View.GONE
83+
candidatesUi.update(RimeProto.Context.Menu())
84+
}
85+
}
86+
}
87+
}
7088
}
7189
}
7290

7391
init {
74-
// invisible by default
75-
// visibility = GONE
76-
7792
verticalPadding = dp(theme.generalStyle.layout.marginX)
7893
horizontalPadding = dp(theme.generalStyle.layout.marginY)
7994
add(

app/src/main/java/com/osfans/trime/ime/composition/CompositionPopupWindow.kt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ import android.os.Build.VERSION_CODES
1111
import android.os.Handler
1212
import android.os.Looper
1313
import android.view.Gravity
14-
import android.view.View.MeasureSpec
14+
import android.view.View
1515
import android.view.ViewGroup
1616
import android.view.WindowManager
1717
import android.view.inputmethod.CursorAnchorInfo
1818
import android.widget.PopupWindow
1919
import androidx.core.math.MathUtils
2020
import com.osfans.trime.core.RimeProto
2121
import com.osfans.trime.daemon.RimeSession
22-
import com.osfans.trime.data.prefs.AppPrefs
2322
import com.osfans.trime.data.theme.ColorManager
2423
import com.osfans.trime.data.theme.Theme
2524
import com.osfans.trime.ime.bar.QuickBar
@@ -38,11 +37,6 @@ class CompositionPopupWindow(
3837
private val theme: Theme,
3938
private val bar: QuickBar,
4039
) : InputBroadcastReceiver {
41-
// 顯示懸浮窗口
42-
val isPopupWindowEnabled =
43-
AppPrefs.defaultInstance().keyboard.popupWindowEnabled &&
44-
theme.generalStyle.window.isNotEmpty()
45-
4640
val root = CandidatesView(ctx, rime, theme)
4741

4842
// 悬浮窗口是否可移動
@@ -60,7 +54,7 @@ class CompositionPopupWindow(
6054
// 悬浮窗口彈出位置
6155
private var popupWindowPos = PopupPosition.fromString(theme.generalStyle.layout.position)
6256

63-
private val mPopupWindow by lazy {
57+
private val mPopupWindow =
6458
PopupWindow(root).apply {
6559
isClippingEnabled = false
6660
inputMethodMode = PopupWindow.INPUT_METHOD_NOT_NEEDED
@@ -86,7 +80,6 @@ class CompositionPopupWindow(
8680
.toFloat(),
8781
)
8882
}
89-
}
9083

9184
var isCursorUpdated = false // 光標是否移動
9285

@@ -95,15 +88,15 @@ class CompositionPopupWindow(
9588

9689
private val mPopupTimer =
9790
Runnable {
98-
if (!isPopupWindowEnabled || bar.view.windowToken == null) return@Runnable
91+
if (bar.view.windowToken == null) return@Runnable
9992
bar.view.let { anchor ->
10093
var x = 0
10194
var y = 0
10295
val (_, anchorY) =
10396
intArrayOf(0, 0).also {
10497
anchor.getLocationInWindow(it)
10598
}
106-
root.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
99+
root.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
107100
root.requestLayout()
108101
val selfWidth = root.width
109102
val selfHeight = root.height
@@ -176,6 +169,7 @@ class CompositionPopupWindow(
176169

177170
override fun onInputContextUpdate(ctx: RimeProto.Context) {
178171
if (ctx.composition.length > 0) {
172+
root.update(ctx)
179173
updateCompositionView()
180174
} else {
181175
hideCompositionView()

0 commit comments

Comments
 (0)