Fix seek bar for RTL

This commit is contained in:
Damontecres 2026-05-16 12:44:54 -04:00
parent 7ff4b0cf10
commit 01e0e7028a
No known key found for this signature in database
2 changed files with 133 additions and 78 deletions

View file

@ -32,6 +32,14 @@ fun isControllerMedia(event: KeyEvent) =
event.key == Key.ButtonL1 || event.key == Key.ButtonL1 ||
event.key == Key.ButtonL2 event.key == Key.ButtonL2
fun isDpadLeft(event: KeyEvent) =
event.key == Key.DirectionLeft ||
event.key == Key.SystemNavigationLeft
fun isDpadRight(event: KeyEvent) =
event.key == Key.DirectionRight ||
event.key == Key.SystemNavigationRight
fun isSkipBack(event: KeyEvent) = fun isSkipBack(event: KeyEvent) =
event.key == Key.DirectionLeft || event.key == Key.DirectionLeft ||
event.key == Key.ButtonL1 || event.key == Key.ButtonL1 ||

View file

@ -18,7 +18,6 @@ package com.github.damontecres.wholphin.ui.playback.overlay
* limitations under the License. * limitations under the License.
*/ */
import android.view.KeyEvent
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
@ -44,11 +43,16 @@ import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.ui.playback.ControllerViewState import com.github.damontecres.wholphin.ui.playback.ControllerViewState
import com.github.damontecres.wholphin.ui.playback.calculateSeekAccelerationMultiplier import com.github.damontecres.wholphin.ui.playback.calculateSeekAccelerationMultiplier
import com.github.damontecres.wholphin.ui.playback.isDpadLeft
import com.github.damontecres.wholphin.ui.playback.isDpadRight
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import timber.log.Timber
import kotlin.time.Duration import kotlin.time.Duration
/** /**
@ -174,6 +178,7 @@ private fun SeekBarDisplay(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val color = MaterialTheme.colorScheme.border val color = MaterialTheme.colorScheme.border
val onSurface = MaterialTheme.colorScheme.onSurface val onSurface = MaterialTheme.colorScheme.onSurface
@ -192,70 +197,75 @@ private fun SeekBarDisplay(
.height(animatedIndicatorHeight) .height(animatedIndicatorHeight)
.padding(horizontal = 4.dp) .padding(horizontal = 4.dp)
.onPreviewKeyEvent { event -> .onPreviewKeyEvent { event ->
when (event.nativeKeyEvent.keyCode) { if (!isDpadLeft(event) && !isDpadRight(event)) {
KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT -> { Timber.v("Ignoring %s", event)
when (event.type) { return@onPreviewKeyEvent false
KeyEventType.KeyDown -> { }
val repeatCount = event.nativeKeyEvent.repeatCount val seekBack =
if (repeatCount > 0) { if (isLtr) {
leftHandledByRepeat = true isDpadLeft(event)
onLeft.invoke( } else {
calculateSeekAccelerationMultiplier( isDpadRight(event)
repeatCount = repeatCount, }
durationMs = durationMs, if (seekBack) {
), when (event.type) {
) KeyEventType.KeyDown -> {
} else { val repeatCount = event.nativeKeyEvent.repeatCount
leftHandledByRepeat = false if (repeatCount > 0) {
} leftHandledByRepeat = true
} onLeft.invoke(
calculateSeekAccelerationMultiplier(
KeyEventType.KeyUp -> { repeatCount = repeatCount,
if (!leftHandledByRepeat) { durationMs = durationMs,
onLeft.invoke(1) ),
} )
} else {
leftHandledByRepeat = false leftHandledByRepeat = false
} }
else -> {
return@onPreviewKeyEvent false
}
} }
return@onPreviewKeyEvent true
}
KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT -> { KeyEventType.KeyUp -> {
when (event.type) { if (!leftHandledByRepeat) {
KeyEventType.KeyDown -> { onLeft.invoke(1)
val repeatCount = event.nativeKeyEvent.repeatCount
if (repeatCount > 0) {
rightHandledByRepeat = true
onRight.invoke(
calculateSeekAccelerationMultiplier(
repeatCount = repeatCount,
durationMs = durationMs,
),
)
} else {
rightHandledByRepeat = false
}
} }
leftHandledByRepeat = false
}
KeyEventType.KeyUp -> { else -> {
if (!rightHandledByRepeat) { return@onPreviewKeyEvent false
onRight.invoke(1) }
} }
return@onPreviewKeyEvent true
} else {
when (event.type) {
KeyEventType.KeyDown -> {
val repeatCount = event.nativeKeyEvent.repeatCount
if (repeatCount > 0) {
rightHandledByRepeat = true
onRight.invoke(
calculateSeekAccelerationMultiplier(
repeatCount = repeatCount,
durationMs = durationMs,
),
)
} else {
rightHandledByRepeat = false rightHandledByRepeat = false
} }
else -> {
return@onPreviewKeyEvent false
}
} }
return@onPreviewKeyEvent true
KeyEventType.KeyUp -> {
if (!rightHandledByRepeat) {
onRight.invoke(1)
}
rightHandledByRepeat = false
}
else -> {
return@onPreviewKeyEvent false
}
} }
return@onPreviewKeyEvent true
} }
false
}.focusable(enabled = enabled, interactionSource = interactionSource), }.focusable(enabled = enabled, interactionSource = interactionSource),
onDraw = { onDraw = {
val yOffset = size.height.div(2) val yOffset = size.height.div(2)
@ -266,33 +276,70 @@ private fun SeekBarDisplay(
strokeWidth = size.height, strokeWidth = size.height,
cap = StrokeCap.Round, cap = StrokeCap.Round,
) )
drawLine( if (isLtr) {
color = onSurface.copy(alpha = .65f), drawLine(
start = Offset(x = 0f, y = yOffset), color = onSurface.copy(alpha = .65f),
end = start = Offset(x = 0f, y = yOffset),
Offset( end =
x = size.width.times(bufferedProgress), Offset(
y = yOffset, x = size.width.times(bufferedProgress),
), y = yOffset,
strokeWidth = size.height, ),
cap = StrokeCap.Round, strokeWidth = size.height,
) cap = StrokeCap.Round,
drawLine( )
color = color, drawLine(
start = Offset(x = 0f, y = yOffset), color = color,
end = start = Offset(x = 0f, y = yOffset),
Offset( end =
// x = size.width.times(if (isSelected) seekProgress else progress), Offset(
x = size.width.times(progress), x = size.width.times(progress),
y = yOffset, y = yOffset,
), ),
strokeWidth = size.height, strokeWidth = size.height,
cap = StrokeCap.Round, cap = StrokeCap.Round,
) )
} else {
drawLine(
color = onSurface.copy(alpha = .65f),
start =
Offset(
x = size.width - size.width.times(bufferedProgress),
y = yOffset,
),
end =
Offset(
x = size.width,
y = yOffset,
),
strokeWidth = size.height,
cap = StrokeCap.Round,
)
drawLine(
color = color,
start = Offset(x = size.width - size.width.times(progress), y = yOffset),
end =
Offset(
x = size.width,
y = yOffset,
),
strokeWidth = size.height,
cap = StrokeCap.Round,
)
}
drawCircle( drawCircle(
color = Color.White, color = Color.White,
radius = size.height + 2, radius = size.height + 2,
center = Offset(x = size.width.times(progress), y = yOffset), center =
Offset(
x =
if (isLtr) {
size.width.times(progress)
} else {
size.width - size.width.times(progress)
},
y = yOffset,
),
) )
}, },
) )