Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ enum class CursorSpeed(
SLOW(R.string.cursor_speed_slow, 40f),
MEDIUM(R.string.cursor_speed_medium, 25f),
FAST(R.string.cursor_speed_fast, 15f),
VERY_FAST(R.string.cursor_speed_very_fast, 8f),
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,118 @@ package com.urik.keyboard.ui.keyboard.components
import android.os.VibrationEffect

sealed class HapticSignature {
abstract val durationMs: Long

abstract fun createEffect(baseAmplitude: Int): VibrationEffect

protected fun createAmplitudeEffect(
timings: LongArray,
amplitudes: IntArray,
totalDurationMs: Long,
baseAmplitude: Int,
): VibrationEffect =
if (baseAmplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
VibrationEffect.createOneShot(totalDurationMs, VibrationEffect.DEFAULT_AMPLITUDE)
} else {
VibrationEffect.createWaveform(timings, amplitudes, -1)
}

data object LetterClick : HapticSignature() {
override val durationMs = 25L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val amplitude = baseAmplitude.coerceIn(1, 255)
return VibrationEffect.createWaveform(
return createAmplitudeEffect(
longArrayOf(0, 25),
intArrayOf(0, amplitude),
-1,
durationMs,
baseAmplitude,
)
}
}

data object SpaceThump : HapticSignature() {
override val durationMs = 35L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val peakAmplitude = baseAmplitude.coerceIn(1, 255)
val startAmplitude = (peakAmplitude * 0.5).toInt()
return VibrationEffect.createWaveform(
return createAmplitudeEffect(
longArrayOf(0, 15, 20),
intArrayOf(0, startAmplitude, peakAmplitude),
-1,
durationMs,
baseAmplitude,
)
}
}

data object BackspaceChirp : HapticSignature() {
override val durationMs = 28L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val startAmplitude = baseAmplitude.coerceIn(1, 255)
val endAmplitude = (startAmplitude * 0.4).toInt()
return VibrationEffect.createWaveform(
return createAmplitudeEffect(
longArrayOf(0, 14, 14),
intArrayOf(0, startAmplitude, endAmplitude),
-1,
durationMs,
baseAmplitude,
)
}
}

data object ShiftPulse : HapticSignature() {
override val durationMs = 42L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val amplitude = (baseAmplitude * 0.9).toInt().coerceIn(1, 255)
return VibrationEffect.createWaveform(
return createAmplitudeEffect(
longArrayOf(0, 15, 12, 15),
intArrayOf(0, amplitude, 0, amplitude),
-1,
durationMs,
baseAmplitude,
)
}
}

data object EnterCompletion : HapticSignature() {
override val durationMs = 51L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val peakAmplitude = baseAmplitude.coerceIn(1, 255)
val midAmplitude = (peakAmplitude * 0.7).toInt()
return VibrationEffect.createWaveform(
return createAmplitudeEffect(
longArrayOf(0, 12, 25, 14),
intArrayOf(0, midAmplitude, peakAmplitude, midAmplitude),
-1,
durationMs,
baseAmplitude,
)
}
}

data object PunctuationTick : HapticSignature() {
override val durationMs = 18L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val amplitude = (baseAmplitude * 0.7).toInt().coerceIn(1, 255)
return VibrationEffect.createWaveform(
return createAmplitudeEffect(
longArrayOf(0, 18),
intArrayOf(0, amplitude),
-1,
durationMs,
baseAmplitude,
)
}
}

data object NumberClick : HapticSignature() {
override val durationMs = 25L

override fun createEffect(baseAmplitude: Int): VibrationEffect {
val amplitude = (baseAmplitude * 0.85).toInt().coerceIn(1, 255)
val amplitude = if (baseAmplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
VibrationEffect.DEFAULT_AMPLITUDE
} else {
(baseAmplitude * 0.85).toInt().coerceIn(1, 255)
}
return VibrationEffect.createOneShot(25L, amplitude)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ class KeyboardLayoutManager(
@Suppress("DEPRECATION")
context.getSystemService(Context.VIBRATOR_SERVICE) as? android.os.Vibrator
}
private val supportsAmplitudeControl = vibrator?.hasAmplitudeControl() == true
private val vibrationAttributes =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
android.os.VibrationAttributes.createForUsage(android.os.VibrationAttributes.USAGE_TOUCH)
} else {
null
}
private var hapticEnabled = true
private var hapticAmplitude = 170
private var shiftLongPressFired = false
Expand Down Expand Up @@ -629,11 +636,9 @@ class KeyboardLayoutManager(

fun triggerBackspaceHaptic() {
if (!hapticEnabled || hapticAmplitude == 0) return
try {
val effect = HapticSignature.BackspaceChirp.createEffect(hapticAmplitude)
vibrator?.vibrate(effect)
} catch (_: Exception) {
}
val amplitude = if (supportsAmplitudeControl) hapticAmplitude else android.os.VibrationEffect.DEFAULT_AMPLITUDE
val effect = HapticSignature.BackspaceChirp.createEffect(amplitude)
vibrateEffect(effect)
}

fun forceStopAcceleratedBackspace() {
Expand Down Expand Up @@ -718,6 +723,18 @@ class KeyboardLayoutManager(
customKeyMappings = mappings
}

private fun vibrateEffect(effect: android.os.VibrationEffect) {
val v = vibrator ?: return
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU && vibrationAttributes != null) {
v.vibrate(effect, vibrationAttributes)
} else {
v.vibrate(effect)
}
} catch (_: Exception) {
}
}

private fun performContextualHaptic(key: KeyboardKey?) {
if (!hapticEnabled || hapticAmplitude == 0) return

Expand Down Expand Up @@ -752,8 +769,9 @@ class KeyboardLayoutManager(
}
}

val effect = signature.createEffect(hapticAmplitude)
vibrator?.vibrate(effect)
val amplitude = if (supportsAmplitudeControl) hapticAmplitude else android.os.VibrationEffect.DEFAULT_AMPLITUDE
val effect = signature.createEffect(amplitude)
vibrateEffect(effect)
} catch (_: Exception) {
}
}
Expand Down Expand Up @@ -1811,10 +1829,14 @@ class KeyboardLayoutManager(
val phaseProgress = elapsed / 500f
val intervalMs = (80 - phaseProgress * 20).toLong().coerceAtLeast(60)
val intensity = 0.4f + phaseProgress * 0.3f
val amplitude = (hapticAmplitude * intensity).toInt().coerceIn(1, 255)
val amplitude = if (supportsAmplitudeControl) {
(hapticAmplitude * intensity).toInt().coerceIn(1, 255)
} else {
android.os.VibrationEffect.DEFAULT_AMPLITUDE
}

withContext(Dispatchers.Main) {
vibrator?.vibrate(
vibrateEffect(
android.os.VibrationEffect.createOneShot(intervalMs / 2, amplitude),
)
}
Expand All @@ -1832,10 +1854,14 @@ class KeyboardLayoutManager(
val phaseProgress = (elapsed - 500) / 1000f
val intervalMs = (60 - phaseProgress * 30).toLong().coerceAtLeast(30)
val intensity = 0.7f + phaseProgress * 0.3f
val amplitude = (hapticAmplitude * intensity).toInt().coerceIn(1, 255)
val amplitude = if (supportsAmplitudeControl) {
(hapticAmplitude * intensity).toInt().coerceIn(1, 255)
} else {
android.os.VibrationEffect.DEFAULT_AMPLITUDE
}

withContext(Dispatchers.Main) {
vibrator?.vibrate(
vibrateEffect(
android.os.VibrationEffect.createOneShot(intervalMs / 2, amplitude),
)
}
Expand Down Expand Up @@ -1873,21 +1899,22 @@ class KeyboardLayoutManager(
}

val intensity = 0.4f + progress * 0.7f
(hapticAmplitude * intensity).toInt().coerceIn(1, 255)
if (supportsAmplitudeControl) {
(hapticAmplitude * intensity).toInt().coerceIn(1, 255)
} else {
android.os.VibrationEffect.DEFAULT_AMPLITUDE
}
}

backgroundScope.launch {
withContext(Dispatchers.Main) {
try {
vibrator?.vibrate(
android.os.VibrationEffect.createWaveform(
timings,
amplitudes,
rampSteps + 2,
),
)
} catch (_: Exception) {
}
vibrateEffect(
android.os.VibrationEffect.createWaveform(
timings,
amplitudes,
rampSteps + 2,
),
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class SwipeKeyboardView
private var gestureStartY = 0f
private var gestureLastProcessedX = 0f
private var gestureDensity = 1f
private var gesturePrevX = 0f
private var gesturePrevTime = 0L
private var currentCursorSpeed: com.urik.keyboard.settings.CursorSpeed = com.urik.keyboard.settings.CursorSpeed.MEDIUM

private var confirmationOverlay: FrameLayout? = null
Expand Down Expand Up @@ -1672,9 +1674,24 @@ class SwipeKeyboardView

when (key.action) {
KeyboardKey.ActionType.SPACE -> {
val totalDx = x - gestureStartX
val sensitivity = currentCursorSpeed.sensitivityDp * gestureDensity
val now = System.currentTimeMillis()
val dt = (now - gesturePrevTime).coerceAtLeast(1)
val velocityPxPerMs = kotlin.math.abs(x - gesturePrevX) / dt.toFloat()
gesturePrevX = x
gesturePrevTime = now

val velocityDpPerMs = velocityPxPerMs / gestureDensity
val accelerationMultiplier = when {
velocityDpPerMs > 1.5f -> 3.0f
velocityDpPerMs > 0.8f -> 2.0f
velocityDpPerMs > 0.4f -> 1.4f
else -> 1.0f
}

val baseSensitivity = currentCursorSpeed.sensitivityDp * gestureDensity
val sensitivity = baseSensitivity / accelerationMultiplier

val totalDx = x - gestureStartX
val positionsToMove = (totalDx / sensitivity).toInt()
val lastPositionsMoved = ((gestureLastProcessedX - gestureStartX) / sensitivity).toInt()
val deltaPositions = positionsToMove - lastPositionsMoved
Expand Down Expand Up @@ -1749,6 +1766,8 @@ class SwipeKeyboardView
gestureStartX = ev.x
gestureStartY = ev.y
gestureLastProcessedX = ev.x
gesturePrevX = ev.x
gesturePrevTime = System.currentTimeMillis()
}

swipeDetector?.handleTouchEvent(ev) { x, y -> findKeyAt(x, y) }
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<string name="cursor_speed_slow">بطيء</string>
<string name="cursor_speed_medium">متوسط</string>
<string name="cursor_speed_fast">سريع</string>
<string name="cursor_speed_very_fast">سريع جداً</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">نحيف</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-ca/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<string name="cursor_speed_slow">Lenta</string>
<string name="cursor_speed_medium">Mitjana</string>
<string name="cursor_speed_fast">Ràpida</string>
<string name="cursor_speed_very_fast">Molt ràpid</string>
<!-- Space Bar Size -->
<string name="space_bar_size_compact">Compacta</string>
<string name="space_bar_size_standard">Estàndard</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-cs/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<string name="cursor_speed_slow">Pomalá</string>
<string name="cursor_speed_medium">Střední</string>
<string name="cursor_speed_fast">Rychlá</string>
<string name="cursor_speed_very_fast">Velmi rychlá</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">Kompaktní</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<string name="cursor_speed_slow">Langsam</string>
<string name="cursor_speed_medium">Mittel</string>
<string name="cursor_speed_fast">Schnell</string>
<string name="cursor_speed_very_fast">Sehr schnell</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">Kompakt</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-el/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<string name="cursor_speed_slow">Αργή</string>
<string name="cursor_speed_medium">Κανονική</string>
<string name="cursor_speed_fast">Γρήγορη</string>
<string name="cursor_speed_very_fast">Πολύ γρήγορο</string>
<!-- Space Bar Size -->
<string name="space_bar_size_compact">Στενό</string>
<string name="space_bar_size_standard">Κανονικό</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<string name="cursor_speed_slow">Lenta</string>
<string name="cursor_speed_medium">Media</string>
<string name="cursor_speed_fast">Rápida</string>
<string name="cursor_speed_very_fast">Muy rápido</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">Compacta</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-fa/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<string name="cursor_speed_slow">کند</string>
<string name="cursor_speed_medium">متوسط</string>
<string name="cursor_speed_fast">سریع</string>
<string name="cursor_speed_very_fast">خیلی سریع</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">جمع‌وجور</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<string name="cursor_speed_slow">Lent</string>
<string name="cursor_speed_medium">Moyen</string>
<string name="cursor_speed_fast">Rapide</string>
<string name="cursor_speed_very_fast">Très rapide</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">Compacte</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<string name="cursor_speed_slow">Lenta</string>
<string name="cursor_speed_medium">Media</string>
<string name="cursor_speed_fast">Veloce</string>
<string name="cursor_speed_very_fast">Molto veloce</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">Compatta</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-nl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<string name="cursor_speed_slow">Traag</string>
<string name="cursor_speed_medium">Gemiddeld</string>
<string name="cursor_speed_fast">Snel</string>
<string name="cursor_speed_very_fast">Zeer snel</string>

<!-- Space Bar Size -->
<string name="space_bar_size_compact">Compact</string>
Expand Down
Loading