mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
Make CandidatesView support candidate action menu (#875)
This commit is contained in:
parent
bb9cdbdbe9
commit
134880d267
@ -5,19 +5,27 @@
|
|||||||
|
|
||||||
package org.fcitx.fcitx5.android.input
|
package org.fcitx.fcitx5.android.input
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
|
import android.widget.PopupMenu
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.text.bold
|
||||||
|
import androidx.core.text.buildSpannedString
|
||||||
|
import androidx.core.text.color
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.fcitx.fcitx5.android.core.FcitxEvent
|
import org.fcitx.fcitx5.android.core.FcitxEvent
|
||||||
import org.fcitx.fcitx5.android.daemon.FcitxConnection
|
import org.fcitx.fcitx5.android.daemon.FcitxConnection
|
||||||
|
import org.fcitx.fcitx5.android.data.InputFeedbacks
|
||||||
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
||||||
import org.fcitx.fcitx5.android.data.theme.Theme
|
import org.fcitx.fcitx5.android.data.theme.Theme
|
||||||
import org.fcitx.fcitx5.android.data.theme.ThemeManager
|
import org.fcitx.fcitx5.android.data.theme.ThemeManager
|
||||||
import org.fcitx.fcitx5.android.data.theme.ThemePrefs
|
import org.fcitx.fcitx5.android.data.theme.ThemePrefs
|
||||||
|
import org.fcitx.fcitx5.android.utils.item
|
||||||
import org.fcitx.fcitx5.android.utils.navbarFrameHeight
|
import org.fcitx.fcitx5.android.utils.navbarFrameHeight
|
||||||
|
import splitties.resources.styledColor
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
abstract class BaseInputView(
|
abstract class BaseInputView(
|
||||||
@ -57,6 +65,42 @@ abstract class BaseInputView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun triggerCandidateAction(idx: Int, actionIdx: Int) {
|
||||||
|
fcitx.runIfReady { triggerCandidateAction(idx, actionIdx) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var candidateActionMenu: PopupMenu? = null
|
||||||
|
|
||||||
|
fun showCandidateActionMenu(idx: Int, text: String, view: View) {
|
||||||
|
candidateActionMenu?.dismiss()
|
||||||
|
candidateActionMenu = null
|
||||||
|
service.lifecycleScope.launch {
|
||||||
|
val actions = fcitx.runOnReady { getCandidateActions(idx) }
|
||||||
|
if (actions.isEmpty()) return@launch
|
||||||
|
InputFeedbacks.hapticFeedback(view, longPress = true)
|
||||||
|
candidateActionMenu = PopupMenu(context, view).apply {
|
||||||
|
menu.add(buildSpannedString {
|
||||||
|
bold {
|
||||||
|
color(context.styledColor(android.R.attr.colorAccent)) {
|
||||||
|
append(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).apply {
|
||||||
|
isEnabled = false
|
||||||
|
}
|
||||||
|
actions.forEach { action ->
|
||||||
|
menu.item(action.text) {
|
||||||
|
triggerCandidateAction(idx, action.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnDismissListener {
|
||||||
|
candidateActionMenu = null
|
||||||
|
}
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val navbarBackground by ThemeManager.prefs.navbarBackground
|
private val navbarBackground by ThemeManager.prefs.navbarBackground
|
||||||
|
|
||||||
protected fun getNavBarBottomInset(windowInsets: WindowInsets): Int {
|
protected fun getNavBarBottomInset(windowInsets: WindowInsets): Int {
|
||||||
|
|||||||
@ -100,6 +100,7 @@ class CandidatesView(
|
|||||||
private val candidatesUi = PagedCandidatesUi(
|
private val candidatesUi = PagedCandidatesUi(
|
||||||
ctx, theme, setupTextView,
|
ctx, theme, setupTextView,
|
||||||
onCandidateClick = { index -> fcitx.launchOnReady { it.select(index) } },
|
onCandidateClick = { index -> fcitx.launchOnReady { it.select(index) } },
|
||||||
|
onCandidateAction = { index, text, view -> showCandidateActionMenu(index, text, view) },
|
||||||
onPrevPage = { fcitx.launchOnReady { it.offsetCandidatePage(-1) } },
|
onPrevPage = { fcitx.launchOnReady { it.offsetCandidatePage(-1) } },
|
||||||
onNextPage = { fcitx.launchOnReady { it.offsetCandidatePage(1) } }
|
onNextPage = { fcitx.launchOnReady { it.offsetCandidatePage(1) } }
|
||||||
)
|
)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class CandidateItemUi(override val ctx: Context, theme: Theme) : Ui {
|
|||||||
background = pressHighlightDrawable(theme.keyPressHighlightColor)
|
background = pressHighlightDrawable(theme.keyPressHighlightColor)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* candidate long press feedback is handled by [org.fcitx.fcitx5.android.input.candidates.horizontal.HorizontalCandidateComponent.showCandidateActionMenu]
|
* candidate long press feedback is handled by [org.fcitx.fcitx5.android.input.BaseInputView.showCandidateActionMenu]
|
||||||
*/
|
*/
|
||||||
longPressFeedbackEnabled = false
|
longPressFeedbackEnabled = false
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import org.fcitx.fcitx5.android.input.candidates.expanded.PagingCandidateViewAda
|
|||||||
import org.fcitx.fcitx5.android.input.candidates.horizontal.HorizontalCandidateComponent
|
import org.fcitx.fcitx5.android.input.candidates.horizontal.HorizontalCandidateComponent
|
||||||
import org.fcitx.fcitx5.android.input.dependency.fcitx
|
import org.fcitx.fcitx5.android.input.dependency.fcitx
|
||||||
import org.fcitx.fcitx5.android.input.dependency.inputMethodService
|
import org.fcitx.fcitx5.android.input.dependency.inputMethodService
|
||||||
|
import org.fcitx.fcitx5.android.input.dependency.inputView
|
||||||
import org.fcitx.fcitx5.android.input.dependency.theme
|
import org.fcitx.fcitx5.android.input.dependency.theme
|
||||||
import org.fcitx.fcitx5.android.input.keyboard.CommonKeyActionListener
|
import org.fcitx.fcitx5.android.input.keyboard.CommonKeyActionListener
|
||||||
import org.fcitx.fcitx5.android.input.keyboard.KeyAction
|
import org.fcitx.fcitx5.android.input.keyboard.KeyAction
|
||||||
@ -47,6 +48,7 @@ abstract class BaseExpandedCandidateWindow<T : BaseExpandedCandidateWindow<T>> :
|
|||||||
protected val service by manager.inputMethodService()
|
protected val service by manager.inputMethodService()
|
||||||
protected val theme by manager.theme()
|
protected val theme by manager.theme()
|
||||||
protected val fcitx by manager.fcitx()
|
protected val fcitx by manager.fcitx()
|
||||||
|
protected val inputView by manager.inputView()
|
||||||
private val commonKeyActionListener: CommonKeyActionListener by manager.must()
|
private val commonKeyActionListener: CommonKeyActionListener by manager.must()
|
||||||
private val bar: KawaiiBarComponent by manager.must()
|
private val bar: KawaiiBarComponent by manager.must()
|
||||||
private val horizontalCandidate: HorizontalCandidateComponent by manager.must()
|
private val horizontalCandidate: HorizontalCandidateComponent by manager.must()
|
||||||
@ -143,7 +145,7 @@ abstract class BaseExpandedCandidateWindow<T : BaseExpandedCandidateWindow<T>> :
|
|||||||
fcitx.launchOnReady { it.select(holder.idx) }
|
fcitx.launchOnReady { it.select(holder.idx) }
|
||||||
}
|
}
|
||||||
holder.itemView.setOnLongClickListener {
|
holder.itemView.setOnLongClickListener {
|
||||||
horizontalCandidate.showCandidateActionMenu(holder)
|
inputView.showCandidateActionMenu(holder.idx, holder.text, holder.ui.root)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ class PagedCandidatesUi(
|
|||||||
val theme: Theme,
|
val theme: Theme,
|
||||||
private val setupTextView: TextView.() -> Unit,
|
private val setupTextView: TextView.() -> Unit,
|
||||||
private val onCandidateClick: (Int) -> Unit,
|
private val onCandidateClick: (Int) -> Unit,
|
||||||
|
private val onCandidateAction: (Int, String, View) -> Unit,
|
||||||
private val onPrevPage: () -> Unit,
|
private val onPrevPage: () -> Unit,
|
||||||
private val onNextPage: () -> Unit
|
private val onNextPage: () -> Unit
|
||||||
) : Ui {
|
) : Ui {
|
||||||
@ -80,6 +81,10 @@ class PagedCandidatesUi(
|
|||||||
holder.ui.root.setOnClickListener {
|
holder.ui.root.setOnClickListener {
|
||||||
onCandidateClick.invoke(position)
|
onCandidateClick.invoke(position)
|
||||||
}
|
}
|
||||||
|
holder.ui.root.setOnLongClickListener { v ->
|
||||||
|
onCandidateAction.invoke(position, candidate.text, v)
|
||||||
|
true
|
||||||
|
}
|
||||||
holder.ui.root.updateLayoutParams<FlexboxLayoutManager.LayoutParams> {
|
holder.ui.root.updateLayoutParams<FlexboxLayoutManager.LayoutParams> {
|
||||||
width = if (isVertical) MATCH_PARENT else WRAP_CONTENT
|
width = if (isVertical) MATCH_PARENT else WRAP_CONTENT
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,22 +8,15 @@ package org.fcitx.fcitx5.android.input.candidates.horizontal
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.ShapeDrawable
|
import android.graphics.drawable.ShapeDrawable
|
||||||
import android.graphics.drawable.shapes.RectShape
|
import android.graphics.drawable.shapes.RectShape
|
||||||
import android.widget.PopupMenu
|
|
||||||
import androidx.core.text.bold
|
|
||||||
import androidx.core.text.buildSpannedString
|
|
||||||
import androidx.core.text.color
|
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.fcitx.fcitx5.android.R
|
import org.fcitx.fcitx5.android.R
|
||||||
import org.fcitx.fcitx5.android.core.FcitxEvent
|
import org.fcitx.fcitx5.android.core.FcitxEvent
|
||||||
import org.fcitx.fcitx5.android.daemon.launchOnReady
|
import org.fcitx.fcitx5.android.daemon.launchOnReady
|
||||||
import org.fcitx.fcitx5.android.data.InputFeedbacks
|
|
||||||
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
||||||
import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.BooleanKey.ExpandedCandidatesEmpty
|
import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.BooleanKey.ExpandedCandidatesEmpty
|
||||||
import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.TransitionEvent.ExpandedCandidatesUpdated
|
import org.fcitx.fcitx5.android.input.bar.ExpandButtonStateMachine.TransitionEvent.ExpandedCandidatesUpdated
|
||||||
@ -38,11 +31,10 @@ import org.fcitx.fcitx5.android.input.dependency.UniqueViewComponent
|
|||||||
import org.fcitx.fcitx5.android.input.dependency.context
|
import org.fcitx.fcitx5.android.input.dependency.context
|
||||||
import org.fcitx.fcitx5.android.input.dependency.fcitx
|
import org.fcitx.fcitx5.android.input.dependency.fcitx
|
||||||
import org.fcitx.fcitx5.android.input.dependency.inputMethodService
|
import org.fcitx.fcitx5.android.input.dependency.inputMethodService
|
||||||
|
import org.fcitx.fcitx5.android.input.dependency.inputView
|
||||||
import org.fcitx.fcitx5.android.input.dependency.theme
|
import org.fcitx.fcitx5.android.input.dependency.theme
|
||||||
import org.fcitx.fcitx5.android.utils.item
|
|
||||||
import org.mechdancer.dependency.manager.must
|
import org.mechdancer.dependency.manager.must
|
||||||
import splitties.dimensions.dp
|
import splitties.dimensions.dp
|
||||||
import splitties.resources.styledColor
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class HorizontalCandidateComponent :
|
class HorizontalCandidateComponent :
|
||||||
@ -52,6 +44,7 @@ class HorizontalCandidateComponent :
|
|||||||
private val context by manager.context()
|
private val context by manager.context()
|
||||||
private val fcitx by manager.fcitx()
|
private val fcitx by manager.fcitx()
|
||||||
private val theme by manager.theme()
|
private val theme by manager.theme()
|
||||||
|
private val inputView by manager.inputView()
|
||||||
private val bar: KawaiiBarComponent by manager.must()
|
private val bar: KawaiiBarComponent by manager.must()
|
||||||
|
|
||||||
private val fillStyle by AppPrefs.getInstance().keyboard.horizontalCandidateStyle
|
private val fillStyle by AppPrefs.getInstance().keyboard.horizontalCandidateStyle
|
||||||
@ -105,7 +98,7 @@ class HorizontalCandidateComponent :
|
|||||||
fcitx.launchOnReady { it.select(holder.idx) }
|
fcitx.launchOnReady { it.select(holder.idx) }
|
||||||
}
|
}
|
||||||
holder.itemView.setOnLongClickListener {
|
holder.itemView.setOnLongClickListener {
|
||||||
showCandidateActionMenu(holder)
|
inputView.showCandidateActionMenu(holder.idx, holder.text, holder.ui.root)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,43 +197,4 @@ class HorizontalCandidateComponent :
|
|||||||
refreshExpanded(0)
|
refreshExpanded(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private fun triggerCandidateAction(idx: Int, actionIdx: Int) {
|
|
||||||
fcitx.runIfReady { triggerCandidateAction(idx, actionIdx) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private var candidateActionMenu: PopupMenu? = null
|
|
||||||
|
|
||||||
fun showCandidateActionMenu(holder: CandidateViewHolder) {
|
|
||||||
val idx = holder.idx
|
|
||||||
val text = holder.text
|
|
||||||
val view = holder.ui.root
|
|
||||||
candidateActionMenu?.dismiss()
|
|
||||||
candidateActionMenu = null
|
|
||||||
service.lifecycleScope.launch {
|
|
||||||
val actions = fcitx.runOnReady { getCandidateActions(idx) }
|
|
||||||
if (actions.isEmpty()) return@launch
|
|
||||||
InputFeedbacks.hapticFeedback(view, longPress = true)
|
|
||||||
candidateActionMenu = PopupMenu(context, view).apply {
|
|
||||||
menu.add(buildSpannedString {
|
|
||||||
bold {
|
|
||||||
color(context.styledColor(android.R.attr.colorAccent)) {
|
|
||||||
append(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).apply {
|
|
||||||
isEnabled = false
|
|
||||||
}
|
|
||||||
actions.forEach { action ->
|
|
||||||
menu.item(action.text) {
|
|
||||||
triggerCandidateAction(idx, action.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setOnDismissListener {
|
|
||||||
candidateActionMenu = null
|
|
||||||
}
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user