move code related to showing more suggestions into MoreSuggestionsView

This commit is contained in:
Helium314 2025-06-01 16:38:59 +02:00
parent 32099748e5
commit 07ea14ea16
2 changed files with 152 additions and 145 deletions

View file

@ -7,14 +7,22 @@ package helium314.keyboard.latin.suggestions
import android.content.Context
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import helium314.keyboard.accessibility.AccessibilityUtils
import helium314.keyboard.keyboard.Key
import helium314.keyboard.keyboard.Keyboard
import helium314.keyboard.keyboard.KeyboardActionListener
import helium314.keyboard.keyboard.MainKeyboardView
import helium314.keyboard.keyboard.PopupKeysKeyboardView
import helium314.keyboard.keyboard.PopupKeysPanel
import helium314.keyboard.latin.R
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo
import helium314.keyboard.latin.SuggestedWords
import helium314.keyboard.latin.suggestions.MoreSuggestions.MoreSuggestionKey
import helium314.keyboard.latin.utils.Log
import kotlin.math.abs
/**
* A view that renders a virtual [MoreSuggestions]. It handles rendering of keys and detecting
@ -24,12 +32,43 @@ class MoreSuggestionsView @JvmOverloads constructor(
context: Context, attrs: AttributeSet?,
defStyle: Int = R.attr.popupKeysKeyboardViewStyle
) : PopupKeysKeyboardView(context, attrs, defStyle) {
abstract class MoreSuggestionsListener : KeyboardActionListener.Adapter() {
abstract fun onSuggestionSelected(wordInfo: SuggestedWordInfo)
private val moreSuggestionsListener = object : KeyboardActionListener.Adapter() {
override fun onCancelInput() {
dismissPopupKeysPanel()
}
}
var isInModalMode = false
private set
private val moreSuggestionsController: PopupKeysPanel.Controller = object : PopupKeysPanel.Controller {
override fun onDismissPopupKeysPanel() {
mainKeyboardView.onDismissPopupKeysPanel()
}
override fun onShowPopupKeysPanel(panel: PopupKeysPanel) {
mainKeyboardView.onShowPopupKeysPanel(panel)
}
override fun onCancelPopupKeysPanel() {
dismissPopupKeysPanel()
}
}
lateinit var listener: SuggestionStripView.Listener
lateinit var mainKeyboardView: MainKeyboardView
private val moreSuggestionsModalTolerance = context.resources.getDimensionPixelOffset(R.dimen.config_more_suggestions_modal_tolerance)
private val moreSuggestionsBuilder by lazy { MoreSuggestions.Builder(context, this) }
lateinit var gestureDetector: GestureDetector
private var isInModalMode = false
// Working variables for onInterceptTouchEvent(MotionEvent) and onTouchEvent(MotionEvent).
private var needsToTransformTouchEventToHoverEvent = false
private var isDispatchingHoverEventToMoreSuggestions = false
private var lastX = 0
private var lastY = 0
private var originX = 0
private var originY = 0
// TODO: Remove redundant override method.
override fun setKeyboard(keyboard: Keyboard) {
@ -50,9 +89,9 @@ class MoreSuggestionsView @JvmOverloads constructor(
updateKeyDrawParams(keyHeight)
}
fun setModalMode() {
private fun setModalMode() {
isInModalMode = true
// Set vertical correction to zero (Reset popup keys keyboard sliding allowance R#dimen.config_popup_keys_keyboard_slide_allowance).
// Set vertical correction to zero (Reset popup keys keyboard sliding allowance R.dimen.config_popup_keys_keyboard_slide_allowance).
mKeyDetector.setKeyboard(keyboard, -paddingLeft.toFloat(), -paddingTop.toFloat())
}
@ -72,11 +111,96 @@ class MoreSuggestionsView @JvmOverloads constructor(
Log.e(TAG, "Selected suggestion has an illegal index: $index")
return
}
if (mListener !is MoreSuggestionsListener) {
Log.e(TAG, "Expected mListener is MoreSuggestionsListener, but found " + mListener.javaClass.name)
listener.pickSuggestionManually(suggestedWords.getInfo(index))
dismissPopupKeysPanel()
}
internal fun show(
suggestedWords: SuggestedWords, fromIndex: Int, container: View,
layoutHelper: SuggestionStripLayoutHelper, parentView: View
): Boolean {
val maxWidth = parentView.width - container.paddingLeft - container.paddingRight
val parentKeyboard = mainKeyboardView.keyboard ?: return false
val keyboard = moreSuggestionsBuilder.layout(
suggestedWords, fromIndex, maxWidth,
(maxWidth * layoutHelper.mMinMoreSuggestionsWidth).toInt(),
layoutHelper.maxMoreSuggestionsRow, parentKeyboard
).build()
setKeyboard(keyboard)
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val pointX = parentView.width / 2
val pointY = -layoutHelper.mMoreSuggestionsBottomGap
showPopupKeysPanel(parentView, moreSuggestionsController, pointX, pointY, moreSuggestionsListener)
originX = lastX
originY = lastY
return true
}
fun shouldInterceptTouchEvent(motionEvent: MotionEvent): Boolean {
if (!isShowingInParent) {
lastX = motionEvent.x.toInt()
lastY = motionEvent.y.toInt()
return gestureDetector.onTouchEvent(motionEvent)
}
if (isInModalMode) {
return false
}
val index = motionEvent.actionIndex
if (abs((motionEvent.getX(index).toInt() - originX).toDouble()) >= moreSuggestionsModalTolerance
|| originY - motionEvent.getY(index).toInt() >= moreSuggestionsModalTolerance
) {
// Decided to be in the sliding suggestion mode only when the touch point has been moved
// upward. Further MotionEvents will be delivered to SuggestionStripView.onTouchEvent.
needsToTransformTouchEventToHoverEvent = AccessibilityUtils.instance.isTouchExplorationEnabled
isDispatchingHoverEventToMoreSuggestions = false
return true
}
if (motionEvent.action == MotionEvent.ACTION_UP || motionEvent.action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode.
setModalMode()
}
return false
}
fun touchEvent(motionEvent: MotionEvent) {
if (!isShowingInParent) {
return // Ignore any touch event while more suggestions panel hasn't been shown.
}
// In the sliding input mode. MotionEvent should be forwarded to MoreSuggestionsView.
val index = motionEvent.actionIndex
val x = translateX(motionEvent.getX(index).toInt())
val y = translateY(motionEvent.getY(index).toInt())
motionEvent.setLocation(x.toFloat(), y.toFloat())
if (!needsToTransformTouchEventToHoverEvent) {
onTouchEvent(motionEvent)
return
}
(mListener as MoreSuggestionsListener).onSuggestionSelected(suggestedWords.getInfo(index))
// In sliding suggestion mode with accessibility mode on, a touch event should be transformed to a hover event.
val onMoreSuggestions = x in 0..<width && y in 0..<height
if (!onMoreSuggestions && !isDispatchingHoverEventToMoreSuggestions) {
// Just drop this touch event because dispatching hover event isn't started yet and
// the touch event isn't on MoreSuggestionsView.
return
}
val hoverAction: Int
if (onMoreSuggestions && !isDispatchingHoverEventToMoreSuggestions) {
// Transform this touch event to a hover enter event and start dispatching a hover event to MoreSuggestionsView.
isDispatchingHoverEventToMoreSuggestions = true
hoverAction = MotionEvent.ACTION_HOVER_ENTER
} else if (motionEvent.actionMasked == MotionEvent.ACTION_UP) {
// Transform this touch event to a hover exit event and stop dispatching a hover event after this.
isDispatchingHoverEventToMoreSuggestions = false
needsToTransformTouchEventToHoverEvent = false
hoverAction = MotionEvent.ACTION_HOVER_EXIT
} else {
// Transform this touch event to a hover move event.
hoverAction = MotionEvent.ACTION_HOVER_MOVE
}
motionEvent.action = hoverAction
onHoverEvent(motionEvent)
}
companion object {

View file

@ -28,11 +28,8 @@ import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.view.isVisible
import helium314.keyboard.accessibility.AccessibilityUtils
import helium314.keyboard.compat.isDeviceLocked
import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.keyboard.MainKeyboardView
import helium314.keyboard.keyboard.PopupKeysPanel
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.AudioAndHapticFeedbackManager
@ -47,7 +44,6 @@ import helium314.keyboard.latin.define.DebugFlags
import helium314.keyboard.latin.settings.DebugSettings
import helium314.keyboard.latin.settings.Defaults
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.ToolbarKey
import helium314.keyboard.latin.utils.ToolbarMode
@ -62,7 +58,6 @@ import helium314.keyboard.latin.utils.removeFirst
import helium314.keyboard.latin.utils.removePinnedKey
import helium314.keyboard.latin.utils.setToolbarButtonsActivatedStateOnPrefChange
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.abs
import kotlin.math.min
class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int) :
@ -170,57 +165,22 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
updateKeys()
}
private val layoutHelper = SuggestionStripLayoutHelper(context, attrs, defStyle, wordViews, dividerViews, debugInfoViews)
private lateinit var listener: Listener
private lateinit var mainKeyboardView: MainKeyboardView
private var suggestedWords = SuggestedWords.getEmptyInstance()
private var startIndexOfMoreSuggestions = 0
private var direction = 1 // 1 if LTR, -1 if RTL
private var isExternalSuggestionVisible = false // Required to disable the more suggestions if other suggestions are visible
// related to more suggestions
// todo: maybe put most of this in a separate class?
private val moreSuggestionsView: MoreSuggestionsView = moreSuggestionsContainer.findViewById(R.id.more_suggestions_view)
private val moreSuggestionsBuilder = MoreSuggestions.Builder(context, moreSuggestionsView) // todo: why actually here?
private val moreSuggestionsModalTolerance = context.resources.getDimensionPixelOffset(R.dimen.config_more_suggestions_modal_tolerance)
private val moreSuggestionsListener = object : MoreSuggestionsListener() {
override fun onSuggestionSelected(wordInfo: SuggestedWordInfo) {
listener.pickSuggestionManually(wordInfo)
dismissMoreSuggestionsPanel()
}
override fun onCancelInput() {
dismissMoreSuggestionsPanel()
}
}
private val moreSuggestionsSlidingListener = object : SimpleOnGestureListener() {
override fun onScroll(down: MotionEvent?, me: MotionEvent, deltaX: Float, deltaY: Float): Boolean {
if (down == null) return false
val dy = me.y - down.y
return if (toolbarContainer.visibility != VISIBLE && deltaY > 0 && dy < 0) showMoreSuggestions()
else false
}
}
private val moreSuggestionsSlidingDetector = GestureDetector(context, moreSuggestionsSlidingListener)
// Working variables for onInterceptTouchEvent(MotionEvent) and onTouchEvent(MotionEvent).
private var lastX = 0
private var lastY = 0
private var originX = 0
private var originY = 0
private var needsToTransformTouchEventToHoverEvent = false
private var isDispatchingHoverEventToMoreSuggestions = false
private val moreSuggestionsController: PopupKeysPanel.Controller = object : PopupKeysPanel.Controller {
override fun onDismissPopupKeysPanel() {
mainKeyboardView.onDismissPopupKeysPanel()
}
override fun onShowPopupKeysPanel(panel: PopupKeysPanel) {
mainKeyboardView.onShowPopupKeysPanel(panel)
}
override fun onCancelPopupKeysPanel() {
dismissMoreSuggestionsPanel()
private val layoutHelper = SuggestionStripLayoutHelper(context, attrs, defStyle, wordViews, dividerViews, debugInfoViews)
private val moreSuggestionsView = moreSuggestionsContainer.findViewById<MoreSuggestionsView>(R.id.more_suggestions_view).apply {
val slidingListener = object : SimpleOnGestureListener() {
override fun onScroll(down: MotionEvent?, me: MotionEvent, deltaX: Float, deltaY: Float): Boolean {
if (down == null) return false
val dy = me.y - down.y
return if (toolbarContainer.visibility != VISIBLE && deltaY > 0 && dy < 0) showMoreSuggestions()
else false
}
}
gestureDetector = GestureDetector(context, slidingListener)
}
// public stuff
@ -230,7 +190,8 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
/** A connection back to the input method. */
fun setListener(newListener: Listener, inputView: View) {
listener = newListener
mainKeyboardView = inputView.findViewById(R.id.keyboard_view)
moreSuggestionsView.listener = newListener
moreSuggestionsView.mainKeyboardView = inputView.findViewById(R.id.keyboard_view)
}
fun setRtl(isRtlLanguage: Boolean) {
@ -316,84 +277,18 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
return true
}
// this is only for moreSuggestionsView
override fun onInterceptTouchEvent(motionEvent: MotionEvent): Boolean {
// Disable More Suggestions if external suggestions are visible
if (isExternalSuggestionVisible) {
return false
}
// Detecting sliding up finger to show MoreSuggestionsView.
if (!moreSuggestionsView.isShowingInParent) {
lastX = motionEvent.x.toInt()
lastY = motionEvent.y.toInt()
return moreSuggestionsSlidingDetector.onTouchEvent(motionEvent)
}
if (moreSuggestionsView.isInModalMode) {
return false
}
val index = motionEvent.actionIndex
if (abs((motionEvent.getX(index).toInt() - originX).toDouble()) >= moreSuggestionsModalTolerance
|| originY - motionEvent.getY(index).toInt() >= moreSuggestionsModalTolerance
) {
// Decided to be in the sliding suggestion mode only when the touch point has been moved
// upward. Further MotionEvents will be delivered to onTouchEvent(MotionEvent).
needsToTransformTouchEventToHoverEvent = AccessibilityUtils.instance.isTouchExplorationEnabled
isDispatchingHoverEventToMoreSuggestions = false
return true
}
if (motionEvent.action == MotionEvent.ACTION_UP || motionEvent.action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode.
moreSuggestionsView.setModalMode()
}
return false
return moreSuggestionsView.shouldInterceptTouchEvent(motionEvent)
}
// this is only for moreSuggestionsView
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
if (!moreSuggestionsView.isShowingInParent) {
// Ignore any touch event while more suggestions panel hasn't been shown.
return true
}
// In the sliding input mode. {@link MotionEvent} should be forwarded to
// {@link MoreSuggestionsView}.
val index = motionEvent.actionIndex
val x = moreSuggestionsView.translateX(motionEvent.getX(index).toInt())
val y = moreSuggestionsView.translateY(motionEvent.getY(index).toInt())
motionEvent.setLocation(x.toFloat(), y.toFloat())
if (!needsToTransformTouchEventToHoverEvent) {
moreSuggestionsView.onTouchEvent(motionEvent)
return true
}
// In sliding suggestion mode with accessibility mode on, a touch event should be
// transformed to a hover event.
val width = moreSuggestionsView.width
val height = moreSuggestionsView.height
val onMoreSuggestions = x in 0..<width && y in 0..<height
if (!onMoreSuggestions && !isDispatchingHoverEventToMoreSuggestions) {
// Just drop this touch event because dispatching hover event isn't started yet and
// the touch event isn't on MoreSuggestionsView.
return true
}
val hoverAction: Int
if (onMoreSuggestions && !isDispatchingHoverEventToMoreSuggestions) {
// Transform this touch event to a hover enter event and start dispatching a hover event to MoreSuggestionsView.
isDispatchingHoverEventToMoreSuggestions = true
hoverAction = MotionEvent.ACTION_HOVER_ENTER
} else if (motionEvent.actionMasked == MotionEvent.ACTION_UP) {
// Transform this touch event to a hover exit event and stop dispatching a hover event after this.
isDispatchingHoverEventToMoreSuggestions = false
needsToTransformTouchEventToHoverEvent = false
hoverAction = MotionEvent.ACTION_HOVER_EXIT
} else {
// Transform this touch event to a hover move event.
hoverAction = MotionEvent.ACTION_HOVER_MOVE
}
motionEvent.action = hoverAction
moreSuggestionsView.onHoverEvent(motionEvent)
moreSuggestionsView.touchEvent(motionEvent)
return true
}
@ -503,25 +398,13 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
}
private fun showMoreSuggestions(): Boolean {
val parentKeyboard = mainKeyboardView.keyboard ?: return false
if (suggestedWords.size() <= startIndexOfMoreSuggestions) {
return false
}
val container = moreSuggestionsContainer
val maxWidth = width - container.paddingLeft - container.paddingRight
val keyboard = moreSuggestionsBuilder.layout(
suggestedWords, startIndexOfMoreSuggestions, maxWidth,
(maxWidth * layoutHelper.mMinMoreSuggestionsWidth).toInt(),
layoutHelper.maxMoreSuggestionsRow, parentKeyboard
).build()
moreSuggestionsView.setKeyboard(keyboard)
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val pointX = width / 2
val pointY = -layoutHelper.mMoreSuggestionsBottomGap
moreSuggestionsView.showPopupKeysPanel(this, moreSuggestionsController, pointX, pointY, moreSuggestionsListener)
originX = lastX
originY = lastY
if (!moreSuggestionsView.show(
suggestedWords, startIndexOfMoreSuggestions, moreSuggestionsContainer, layoutHelper, this
))
return false
for (i in 0..<startIndexOfMoreSuggestions) {
wordViews[i].isPressed = false
}