Skip to content

Commit 5de607a

Browse files
committed
Make CandidatesView more customizable
1 parent f05f682 commit 5de607a

File tree

7 files changed

+140
-55
lines changed

7 files changed

+140
-55
lines changed

app/src/main/java/org/fcitx/fcitx5/android/data/prefs/AppPrefs.kt

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* SPDX-License-Identifier: LGPL-2.1-or-later
3-
* SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors
3+
* SPDX-FileCopyrightText: Copyright 2021-2024 Fcitx5 for Android Contributors
44
*/
55
package org.fcitx.fcitx5.android.data.prefs
66

@@ -268,9 +268,46 @@ class AppPrefs(private val sharedPreferences: SharedPreferences) {
268268
)
269269

270270
val orientation = enumList(
271-
R.string.candidates_orientation, "candidates_window_orientation",
271+
R.string.candidates_orientation,
272+
"candidates_window_orientation",
272273
FloatingCandidatesOrientation.Automatic
273274
)
275+
276+
val windowMinWidth = int(
277+
R.string.candidates_window_min_width,
278+
"candidates_window_min_width",
279+
0,
280+
0,
281+
640,
282+
"dp",
283+
10
284+
)
285+
286+
val windowPadding =
287+
int(R.string.candidates_window_padding, "candidates_window_padding", 4, 0, 32, "dp")
288+
289+
val fontSize =
290+
int(R.string.candidates_font_size, "candidates_window_font_size", 20, 4, 64, "sp")
291+
292+
val itemPaddingVertical: ManagedPreference.PInt
293+
val itemPaddingHorizontal: ManagedPreference.PInt
294+
295+
init {
296+
val (primary, secondary) = twinInt(
297+
R.string.candidates_padding,
298+
R.string.vertical,
299+
"candidates_item_padding_vertical",
300+
2,
301+
R.string.horizontal,
302+
"candidates_item_padding_horizontal",
303+
4,
304+
0,
305+
64,
306+
"dp"
307+
)
308+
itemPaddingVertical = primary
309+
itemPaddingHorizontal = secondary
310+
}
274311
}
275312

276313
inner class Clipboard : ManagedPreferenceCategory(R.string.clipboard, sharedPreferences) {

app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.annotation.SuppressLint
99
import android.view.ViewGroup
1010
import android.view.ViewTreeObserver.OnGlobalLayoutListener
1111
import android.view.ViewTreeObserver.OnPreDrawListener
12+
import android.widget.TextView
1213
import androidx.annotation.Size
1314
import org.fcitx.fcitx5.android.R
1415
import org.fcitx.fcitx5.android.core.FcitxEvent
@@ -21,14 +22,15 @@ import splitties.dimensions.dp
2122
import splitties.views.backgroundColor
2223
import splitties.views.dsl.constraintlayout.below
2324
import splitties.views.dsl.constraintlayout.bottomOfParent
25+
import splitties.views.dsl.constraintlayout.centerHorizontally
2426
import splitties.views.dsl.constraintlayout.lParams
27+
import splitties.views.dsl.constraintlayout.matchConstraints
2528
import splitties.views.dsl.constraintlayout.startOfParent
2629
import splitties.views.dsl.constraintlayout.topOfParent
2730
import splitties.views.dsl.core.add
2831
import splitties.views.dsl.core.withTheme
2932
import splitties.views.dsl.core.wrapContent
3033
import splitties.views.padding
31-
import splitties.views.setPaddingDp
3234

3335
@SuppressLint("ViewConstructor")
3436
class CandidatesView(
@@ -39,7 +41,13 @@ class CandidatesView(
3941

4042
private val ctx = context.withTheme(R.style.Theme_InputViewTheme)
4143

42-
private val orientation by AppPrefs.getInstance().candidates.orientation
44+
private val candidatesPrefs = AppPrefs.getInstance().candidates
45+
private val orientation by candidatesPrefs.orientation
46+
private val windowMinWidth by candidatesPrefs.windowMinWidth
47+
private val windowPadding by candidatesPrefs.windowPadding
48+
private val fontSize by candidatesPrefs.fontSize
49+
private val itemPaddingVertical by candidatesPrefs.itemPaddingVertical
50+
private val itemPaddingHorizontal by candidatesPrefs.itemPaddingHorizontal
4351

4452
private var inputPanel = FcitxEvent.InputPanelEvent.Data()
4553
private var paged = FcitxEvent.PagedCandidateEvent.Data.Empty
@@ -63,11 +71,16 @@ class CandidatesView(
6371
true
6472
}
6573

66-
private val preeditUi = PreeditUi(ctx, theme, setupTextView = {
67-
setPaddingDp(3, 1, 3, 1)
68-
})
74+
private val setupTextView: TextView.() -> Unit = {
75+
textSize = fontSize.toFloat()
76+
val v = dp(itemPaddingVertical)
77+
val h = dp(itemPaddingHorizontal)
78+
setPadding(h, v, h, v)
79+
}
80+
81+
private val preeditUi = PreeditUi(ctx, theme, setupTextView)
6982

70-
private val candidatesUi = PagedCandidatesUi(ctx, theme).apply {
83+
private val candidatesUi = PagedCandidatesUi(ctx, theme, setupTextView).apply {
7184
root.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
7285
}
7386

@@ -135,16 +148,17 @@ class CandidatesView(
135148
// invisible by default
136149
visibility = GONE
137150

138-
// TODO make it customizable
139-
padding = dp(4)
151+
minWidth = dp(windowMinWidth)
152+
padding = dp(windowPadding)
140153
backgroundColor = theme.backgroundColor
141154
add(preeditUi.root, lParams(wrapContent, wrapContent) {
142155
topOfParent()
143156
startOfParent()
144157
})
145-
add(candidatesUi.root, lParams(wrapContent, wrapContent) {
158+
add(candidatesUi.root, lParams(matchConstraints, wrapContent) {
159+
matchConstraintMinWidth = wrapContent
146160
below(preeditUi.root)
147-
startOfParent()
161+
centerHorizontally()
148162
bottomOfParent()
149163
})
150164

app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import org.fcitx.fcitx5.android.daemon.FcitxDaemon
6161
import org.fcitx.fcitx5.android.data.InputFeedbacks
6262
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
6363
import org.fcitx.fcitx5.android.data.prefs.ManagedPreference
64+
import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceProvider
6465
import org.fcitx.fcitx5.android.data.theme.Theme
6566
import org.fcitx.fcitx5.android.data.theme.ThemeManager
6667
import org.fcitx.fcitx5.android.data.theme.ThemePrefs.NavbarBackground
@@ -165,34 +166,44 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
165166
}
166167
}
167168

168-
@Keep
169-
private val recreateInputViewListener = ManagedPreference.OnChangeListener<Any> { _, _ ->
170-
recreateInputView(ThemeManager.activeTheme)
171-
}
172-
173-
@Keep
174-
private val onThemeChangeListener = ThemeManager.OnThemeChangeListener {
175-
recreateInputView(it)
169+
private fun replaceInputView(theme: Theme): InputView {
170+
val newInputView = InputView(this, fcitx, theme)
171+
setInputView(newInputView)
172+
inputDeviceMgr.setInputView(newInputView)
173+
inputView = newInputView
174+
return newInputView
176175
}
177176

178-
private fun setupInputViews(theme: Theme): InputView {
179-
evaluateNavbarUpdates()
180-
val newInputView = InputView(this, fcitx, theme)
177+
private fun replaceCandidateView(theme: Theme): CandidatesView {
181178
val newCandidatesView = CandidatesView(this, fcitx, theme)
182179
// replace CandidatesView manually
183180
contentView.removeView(candidatesView)
184181
// put CandidatesView directly under content view
185182
contentView.addView(newCandidatesView)
186-
inputView = newInputView
183+
inputDeviceMgr.setCandidatesView(newCandidatesView)
187184
candidatesView = newCandidatesView
188-
inputDeviceMgr.setViews(newInputView, newCandidatesView)
189-
return newInputView
185+
return newCandidatesView
190186
}
191187

192-
private fun recreateInputView(theme: Theme) {
193-
// InputView should be first created in `onCreateInputView`
194-
// `setInputView` should only be used to 'replace' current InputView
195-
setInputView(setupInputViews(theme))
188+
private fun replaceInputViews(theme: Theme) {
189+
evaluateNavbarUpdates()
190+
replaceInputView(theme)
191+
replaceCandidateView(theme)
192+
}
193+
194+
@Keep
195+
private val recreateInputViewListener = ManagedPreference.OnChangeListener<Any> { _, _ ->
196+
replaceInputView(ThemeManager.activeTheme)
197+
}
198+
199+
@Keep
200+
private val recreateCandidatesViewListener = ManagedPreferenceProvider.OnChangeListener {
201+
replaceCandidateView(ThemeManager.activeTheme)
202+
}
203+
204+
@Keep
205+
private val onThemeChangeListener = ThemeManager.OnThemeChangeListener {
206+
replaceInputViews(it)
196207
}
197208

198209
/**
@@ -223,6 +234,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
223234
pkgNameCache = PackageNameCache(this)
224235
AppPrefs.getInstance().apply {
225236
keyboard.expandKeypressArea.registerOnChangeListener(recreateInputViewListener)
237+
candidates.registerOnChangeListener(recreateCandidatesViewListener)
226238
advanced.disableAnimation.registerOnChangeListener(recreateInputViewListener)
227239
}
228240
ThemeManager.addOnChangedListener(onThemeChangeListener)
@@ -502,12 +514,10 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
502514
}
503515
}
504516

505-
override fun onCreateInputView(): View {
506-
// onCreateInputView will be called once, when the input area is first displayed,
507-
// during each onConfigurationChanged period.
508-
// That is, onCreateInputView would be called again, after system dark mode changes,
509-
// or screen orientation changes.
510-
return setupInputViews(ThemeManager.activeTheme)
517+
override fun onCreateInputView(): View? {
518+
replaceInputViews(ThemeManager.activeTheme)
519+
// We will call `setInputView` by ourselves. This is fine.
520+
return null
511521
}
512522

513523
override fun setInputView(view: View) {
@@ -535,20 +545,20 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
535545
private var inputViewLocation = intArrayOf(0, 0)
536546

537547
override fun onComputeInsets(outInsets: Insets) {
538-
if (candidatesView?.handleEvents == true) {
548+
if (inputDeviceMgr.isVirtualKeyboard) {
549+
inputView?.keyboardView?.getLocationInWindow(inputViewLocation)
550+
outInsets.apply {
551+
contentTopInsets = inputViewLocation[1]
552+
visibleTopInsets = inputViewLocation[1]
553+
touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE
554+
}
555+
} else {
539556
val h = decorView.height
540557
outInsets.apply {
541558
contentTopInsets = h
542559
visibleTopInsets = h
543560
touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE
544561
}
545-
return
546-
}
547-
inputView?.keyboardView?.getLocationInWindow(inputViewLocation)
548-
outInsets.apply {
549-
contentTopInsets = inputViewLocation[1]
550-
visibleTopInsets = inputViewLocation[1]
551-
touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE
552562
}
553563
}
554564

@@ -962,7 +972,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
962972

963973
@RequiresApi(Build.VERSION_CODES.R)
964974
override fun onInlineSuggestionsResponse(response: InlineSuggestionsResponse): Boolean {
965-
if (!inlineSuggestions || candidatesView?.handleEvents == true) return false
975+
if (!inlineSuggestions || !inputDeviceMgr.isVirtualKeyboard) return false
966976
return inputView?.handleInlineSuggestions(response) == true
967977
}
968978

@@ -1001,6 +1011,7 @@ class FcitxInputMethodService : LifecycleInputMethodService() {
10011011
override fun onDestroy() {
10021012
AppPrefs.getInstance().apply {
10031013
keyboard.expandKeypressArea.unregisterOnChangeListener(recreateInputViewListener)
1014+
candidates.unregisterOnChangeListener(recreateCandidatesViewListener)
10041015
advanced.disableAnimation.unregisterOnChangeListener(recreateInputViewListener)
10051016
}
10061017
ThemeManager.removeOnChangedListener(onThemeChangeListener)

app/src/main/java/org/fcitx/fcitx5/android/input/InputDeviceManager.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ class InputDeviceManager {
1919
private var inputView: InputView? = null
2020
private var candidatesView: CandidatesView? = null
2121

22-
private fun setupViewEvents(isVirtual: Boolean) {
22+
private fun setupInputViewEvents(isVirtual: Boolean) {
2323
inputView?.handleEvents = isVirtual
2424
inputView?.visibility = if (isVirtual) View.VISIBLE else View.GONE
25+
}
26+
27+
private fun setupCandidatesViewEvents(isVirtual: Boolean) {
2528
candidatesView?.handleEvents = !isVirtual
2629
// hide CandidatesView when entering virtual keyboard mode,
2730
// but preserve the visibility when entering physical keyboard mode (in case it's empty)
@@ -30,16 +33,25 @@ class InputDeviceManager {
3033
}
3134
}
3235

36+
private fun setupViewEvents(isVirtual: Boolean) {
37+
setupInputViewEvents(isVirtual)
38+
setupCandidatesViewEvents(isVirtual)
39+
}
40+
3341
var isVirtualKeyboard = true
3442
private set(value) {
3543
field = value
3644
setupViewEvents(value)
3745
}
3846

39-
fun setViews(inputView: InputView, candidatesView: CandidatesView) {
47+
fun setInputView(inputView: InputView) {
4048
this.inputView = inputView
49+
setupInputViewEvents(this.isVirtualKeyboard)
50+
}
51+
52+
fun setCandidatesView(candidatesView: CandidatesView) {
4153
this.candidatesView = candidatesView
42-
setupViewEvents(this.isVirtualKeyboard)
54+
setupCandidatesViewEvents(this.isVirtualKeyboard)
4355
}
4456

4557
private fun applyMode(service: FcitxInputMethodService, useVirtualKeyboard: Boolean) {
@@ -107,6 +119,7 @@ class InputDeviceManager {
107119
}
108120

109121
fun evaluateOnViewClicked(service: FcitxInputMethodService) {
122+
if (!startedInputView) return
110123
val useVirtualKeyboard = when (candidatesViewMode) {
111124
FloatingCandidatesMode.SystemDefault -> service.superEvaluateInputViewShown()
112125
else -> true
@@ -115,6 +128,7 @@ class InputDeviceManager {
115128
}
116129

117130
fun evaluateOnUpdateEditorToolType(toolType: Int, service: FcitxInputMethodService) {
131+
if (!startedInputView) return
118132
val useVirtualKeyboard = when (candidatesViewMode) {
119133
FloatingCandidatesMode.SystemDefault -> service.superEvaluateInputViewShown()
120134
FloatingCandidatesMode.InputDevice ->

app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/LabeledCandidateItemUi.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,23 @@ package org.fcitx.fcitx5.android.input.candidates.floating
77

88
import android.content.Context
99
import android.graphics.Color
10+
import android.widget.TextView
1011
import androidx.core.text.buildSpannedString
1112
import androidx.core.text.color
1213
import org.fcitx.fcitx5.android.core.FcitxEvent
1314
import org.fcitx.fcitx5.android.data.theme.Theme
1415
import splitties.views.backgroundColor
1516
import splitties.views.dsl.core.Ui
1617
import splitties.views.dsl.core.textView
17-
import splitties.views.setPaddingDp
1818

1919
class LabeledCandidateItemUi(
2020
override val ctx: Context,
2121
val theme: Theme,
22-
val textSize: Float = 16f // sp
22+
setupTextView: TextView.() -> Unit
2323
) : Ui {
2424

2525
override val root = textView {
26-
textSize = this@LabeledCandidateItemUi.textSize
27-
setPaddingDp(3, 1, 3, 1)
26+
setupTextView(this)
2827
}
2928

3029
fun update(candidate: FcitxEvent.Candidate, active: Boolean) {

0 commit comments

Comments
 (0)