mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
Implement swipe gestures for number row collapse/expansion
This commit is contained in:
parent
1455b6090b
commit
e70f74e568
@ -78,6 +78,8 @@ import java.util.concurrent.Executor
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.math.min
|
||||
import kotlin.math.abs
|
||||
import timber.log.Timber
|
||||
|
||||
class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(),
|
||||
InputBroadcastReceiver {
|
||||
@ -108,6 +110,10 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
private var isKeyboardLayoutNumber: Boolean = false
|
||||
private var isToolbarManuallyToggled: Boolean = false
|
||||
|
||||
private enum class NumberRowState { Auto, ForceShow, ForceHide }
|
||||
|
||||
private var numberRowState = NumberRowState.Auto
|
||||
|
||||
@Keep
|
||||
private val onClipboardUpdateListener =
|
||||
ClipboardManager.OnClipboardUpdateListener {
|
||||
@ -165,9 +171,10 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
|
||||
private fun evalIdleUiState(fromUser: Boolean = false) {
|
||||
val newState = when {
|
||||
numberRowState == NumberRowState.ForceShow -> IdleUi.State.NumberRow
|
||||
isClipboardFresh -> IdleUi.State.Clipboard
|
||||
isInlineSuggestionPresent -> IdleUi.State.InlineSuggestion
|
||||
isCapabilityFlagsPassword && !isKeyboardLayoutNumber -> IdleUi.State.NumberRow
|
||||
isCapabilityFlagsPassword && !isKeyboardLayoutNumber && numberRowState != NumberRowState.ForceHide -> IdleUi.State.NumberRow
|
||||
/**
|
||||
* state matrix:
|
||||
* expandToolbarByDefault
|
||||
@ -186,13 +193,40 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
service.requestHideSelf(0)
|
||||
}
|
||||
|
||||
private val swipeDownHideKeyboardCallback = CustomGestureView.OnGestureListener { _, e ->
|
||||
private val swipeDownExpandCallback = CustomGestureView.OnGestureListener { _, e ->
|
||||
if (e.type == CustomGestureView.GestureType.Up && e.totalY > 0) {
|
||||
service.requestHideSelf(0)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
// Combined gesture: determine primary direction by comparing totalX and totalY.
|
||||
// - If horizontal is dominant and left, show number row (when allowed).
|
||||
// - If vertical is dominant and down, hide keyboard.
|
||||
private val swipeHideKeyboardCallback = CustomGestureView.OnGestureListener { _, e ->
|
||||
if (e.type != CustomGestureView.GestureType.Up) return@OnGestureListener false
|
||||
|
||||
val absX = abs(e.totalX)
|
||||
val absY = abs(e.totalY)
|
||||
|
||||
when {
|
||||
// Horizontal && left (show number row)
|
||||
absX > absY && e.totalX < 0 -> {
|
||||
if (isCapabilityFlagsPassword && !isKeyboardLayoutNumber) {
|
||||
numberRowState = NumberRowState.ForceShow
|
||||
evalIdleUiState(fromUser = true)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
// Vertical && down (hide keyboard)
|
||||
absY > absX && e.totalY > 0 -> {
|
||||
service.requestHideSelf(0)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private var voiceInputSubtype: Pair<String, InputMethodSubtype>? = null
|
||||
|
||||
private val switchToVoiceInputCallback = View.OnClickListener {
|
||||
@ -226,7 +260,8 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
setOnClickListener(hideKeyboardCallback)
|
||||
swipeEnabled = true
|
||||
swipeThresholdY = dp(HEIGHT.toFloat())
|
||||
onGestureListener = swipeDownHideKeyboardCallback
|
||||
swipeThresholdX = swipeThresholdY
|
||||
onGestureListener = swipeHideKeyboardCallback
|
||||
}
|
||||
buttonsUi.apply {
|
||||
undoButton.setOnClickListener {
|
||||
@ -262,6 +297,18 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
true
|
||||
}
|
||||
}
|
||||
numberRow.apply {
|
||||
shouldCollapse = { start, current ->
|
||||
val dir = if (context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) 1 else -1
|
||||
val sx = start.x * dir
|
||||
val cx = current.x * dir
|
||||
cx > sx && abs(cx - sx) > dp(HEIGHT.toFloat())
|
||||
}
|
||||
onCollapseListener = {
|
||||
numberRowState = NumberRowState.ForceHide
|
||||
evalIdleUiState(fromUser = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +317,7 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
expandButton.apply {
|
||||
swipeEnabled = true
|
||||
swipeThresholdY = dp(HEIGHT.toFloat())
|
||||
onGestureListener = swipeDownHideKeyboardCallback
|
||||
onGestureListener = swipeDownExpandCallback
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -369,6 +416,7 @@ class KawaiiBarComponent : UniqueViewComponent<KawaiiBarComponent, FrameLayout>(
|
||||
}
|
||||
isCapabilityFlagsPassword = toolbarNumRowOnPassword && capFlags.has(CapabilityFlag.Password)
|
||||
isInlineSuggestionPresent = false
|
||||
numberRowState = NumberRowState.Auto
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
idleUi.inlineSuggestionsBar.clear()
|
||||
}
|
||||
|
||||
@ -8,12 +8,80 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import org.fcitx.fcitx5.android.core.KeySym
|
||||
import org.fcitx.fcitx5.android.data.theme.Theme
|
||||
import android.graphics.PointF
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import org.fcitx.fcitx5.android.input.keyboard.BaseKeyboard
|
||||
import org.fcitx.fcitx5.android.input.keyboard.KeyAction
|
||||
import org.fcitx.fcitx5.android.input.keyboard.KeyActionListener
|
||||
import org.fcitx.fcitx5.android.input.keyboard.KeyDef
|
||||
import org.fcitx.fcitx5.android.input.keyboard.CustomGestureView
|
||||
import org.fcitx.fcitx5.android.input.popup.PopupActionListener
|
||||
import kotlin.math.abs
|
||||
import timber.log.Timber
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class NumberRow(ctx: Context, theme: Theme) : BaseKeyboard(ctx, theme, Layout) {
|
||||
|
||||
private var gestureStartEvent: MotionEvent? = null
|
||||
private var collapseGestureTriggerd: Boolean = false
|
||||
|
||||
var onCollapseListener: (() -> Unit)? = null
|
||||
|
||||
// return true if the swipe distance is enough to trigger collapse
|
||||
var shouldCollapse: (start: PointF, current: PointF) -> Boolean = { _, _ -> false }
|
||||
|
||||
private fun checkGesture(ev: MotionEvent): Boolean {
|
||||
val startEvent = gestureStartEvent ?: return false
|
||||
val firstPointerId = startEvent.getPointerId(startEvent.actionIndex)
|
||||
if (ev.getPointerId(ev.actionIndex) == firstPointerId) {
|
||||
val start = PointF(startEvent.x, startEvent.y)
|
||||
val current = PointF(ev.getX(ev.actionIndex), ev.getY(ev.actionIndex))
|
||||
if (shouldCollapse(start, current)) {
|
||||
Timber.d("NumberRow: intercepted gesture from child keyboard to handle swipe")
|
||||
resetState()
|
||||
collapseGestureTriggerd = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resetState() {
|
||||
gestureStartEvent?.recycle()
|
||||
gestureStartEvent = null
|
||||
collapseGestureTriggerd = false
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> gestureStartEvent = MotionEvent.obtain(ev)
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (checkGesture(ev)) return true
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> resetState()
|
||||
}
|
||||
return super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
var handled = false
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> gestureStartEvent = MotionEvent.obtain(ev)
|
||||
MotionEvent.ACTION_MOVE -> checkGesture(ev)
|
||||
MotionEvent.ACTION_UP -> {
|
||||
if (collapseGestureTriggerd) {
|
||||
resetState()
|
||||
onCollapseListener?.invoke()
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> resetState()
|
||||
}
|
||||
return super.onTouchEvent(ev) || handled
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Layout = listOf(
|
||||
listOf("1", "2", "3", "4", "5", "6", "7", "8", "9", "0").map { digit ->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user