Merge pull request #1153 from silentbil/fix-hebrew

Fix Hebrew translations and RTL player seek direction
This commit is contained in:
Nayif 2026-03-31 23:12:31 +05:30 committed by GitHub
commit 9951620f1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 77 additions and 28 deletions

View file

@ -573,6 +573,7 @@ private fun LegacySidebarScaffold(
val openDrawerWidth = 196.dp
val focusManager = LocalFocusManager.current
val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
val contentFocusRequester = remember { FocusRequester() }
var pendingContentFocusTransfer by remember { mutableStateOf(false) }
var pendingSidebarFocusRequest by remember { mutableStateOf(false) }
@ -625,7 +626,8 @@ private fun LegacySidebarScaffold(
.padding(12.dp)
.selectableGroup()
.onPreviewKeyEvent { keyEvent ->
if (keyEvent.key == Key.DirectionRight && keyEvent.type == KeyEventType.KeyDown) {
val closeKey = if (isRtl) Key.DirectionLeft else Key.DirectionRight
if (keyEvent.key == closeKey && keyEvent.type == KeyEventType.KeyDown) {
drawerState.setValue(DrawerValue.Closed)
pendingContentFocusTransfer = false
true
@ -741,13 +743,14 @@ private fun LegacySidebarScaffold(
.fillMaxSize()
.padding(start = contentStartPadding)
.onKeyEvent { keyEvent ->
val openKey = if (isRtl) Key.DirectionRight else Key.DirectionLeft
if (
showSidebar &&
drawerState.currentValue == DrawerValue.Closed &&
keyEvent.type == KeyEventType.KeyDown &&
keyEvent.key == Key.DirectionLeft
keyEvent.key == openKey
) {
if (focusManager.moveFocus(FocusDirection.Left)) {
if (focusManager.moveFocus(if (isRtl) FocusDirection.Right else FocusDirection.Left)) {
true
} else {
pendingSidebarFocusRequest = true
@ -875,6 +878,7 @@ private fun ModernSidebarScaffold(
val openSidebarWidth = 262.dp
val focusManager = LocalFocusManager.current
val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
val contentFocusRequester = remember { FocusRequester() }
val drawerItemFocusRequesters = remember(drawerItems) {
drawerItems.associate { item -> item.route to FocusRequester() }
@ -1092,8 +1096,9 @@ private fun ModernSidebarScaffold(
else -> Unit
}
}
if (keyEvent.key == Key.DirectionLeft) {
if (focusManager.moveFocus(FocusDirection.Left)) {
val openKey = if (isRtl) Key.DirectionRight else Key.DirectionLeft
if (keyEvent.key == openKey) {
if (focusManager.moveFocus(if (isRtl) FocusDirection.Right else FocusDirection.Left)) {
true
} else {
isSidebarExpanded = true
@ -1156,10 +1161,15 @@ private fun ModernSidebarScaffold(
focusedDrawerIndex == drawerItems.lastIndex
}
Key.DirectionRight -> {
pendingContentFocusTransfer = false
sidebarCollapsePending = true
true
Key.DirectionRight, Key.DirectionLeft -> {
val collapseKey = if (isRtl) Key.DirectionLeft else Key.DirectionRight
if (keyEvent.key == collapseKey) {
pendingContentFocusTransfer = false
sidebarCollapsePending = true
true
} else {
false
}
}
else -> false

View file

@ -111,6 +111,9 @@ import android.text.format.DateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import kotlinx.coroutines.delay
@Composable
@ -121,6 +124,8 @@ fun PlayerScreen(
onPlaybackEnded: ((nextVideoId: String?, nextSeason: Int?, nextEpisode: Int?) -> Unit)? = null
) {
val uiState by viewModel.uiState.collectAsState()
val layoutDirection = LocalLayoutDirection.current
val isRtl = layoutDirection == LayoutDirection.Rtl
val lifecycleOwner = LocalLifecycleOwner.current
val containerFocusRequester = remember { FocusRequester() }
val playPauseFocusRequester = remember { FocusRequester() }
@ -417,11 +422,8 @@ fun PlayerScreen(
repeatCount >= 3 -> 20_000L
else -> 10_000L
}
val deltaMs = if (keyEvent.nativeKeyEvent.keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
-stepMs
} else {
stepMs
}
val isLeft = keyEvent.nativeKeyEvent.keyCode == KeyEvent.KEYCODE_DPAD_LEFT
val deltaMs = if (isLeft xor isRtl) -stepMs else stepMs
viewModel.onEvent(PlayerEvent.OnPreviewSeekBy(deltaMs))
true
} else {
@ -1029,6 +1031,7 @@ private fun PlayerControlsOverlay(
onBack: () -> Unit,
skipButtonVisible: Boolean = false
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
val customPlayPainter = rememberRawSvgPainter(R.raw.ic_player_play)
val customPausePainter = rememberRawSvgPainter(R.raw.ic_player_pause)
val customSubtitlePainter = rememberRawSvgPainter(R.raw.ic_player_subtitles)
@ -1152,12 +1155,13 @@ private fun PlayerControlsOverlay(
ProgressBar(
currentPosition = uiState.pendingPreviewSeekPosition ?: uiState.currentPosition,
duration = uiState.duration,
onSeekPreview = { delta ->
onSeekPreview = { delta ->
viewModel.onEvent(PlayerEvent.OnPreviewSeekBy(delta))
},
onSeekCommit = {
onSeekCommit = {
viewModel.onEvent(PlayerEvent.OnCommitPreviewSeek)
},
isRtl = isRtl,
focusRequester = progressBarFocusRequester,
upFocusRequester = progressBarUpFocusRequester,
downFocusRequester = playPauseFocusRequester,
@ -1414,8 +1418,9 @@ private fun ControlButton(
private fun ProgressBar(
currentPosition: Long,
duration: Long,
onSeekPreview: (Long) -> Unit,
onSeekCommit: () -> Unit,
onSeekPreview: (Long) -> Unit,
onSeekCommit: () -> Unit,
isRtl: Boolean = false,
focusRequester: FocusRequester? = null,
upFocusRequester: FocusRequester? = null,
downFocusRequester: FocusRequester? = null,
@ -1493,11 +1498,11 @@ private fun ProgressBar(
}
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
onSeekPreview(-10_000L)
onSeekPreview(if (isRtl) 10_000L else -10_000L)
true
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
onSeekPreview(10_000L)
onSeekPreview(if (isRtl) -10_000L else 10_000L)
true
}
else -> false

View file

@ -202,6 +202,7 @@ fun SettingsScreen(
}
}
val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
var selectedCategory by remember(visibleSections) {
mutableStateOf(
visibleSections.firstOrNull()?.category ?: SettingsCategory.APPEARANCE
@ -298,7 +299,8 @@ fun SettingsScreen(
}
}
.onPreviewKeyEvent { event ->
if (event.type == KeyEventType.KeyDown && event.key == Key.DirectionRight) {
val toDetailKey = if (isRtl) Key.DirectionLeft else Key.DirectionRight
if (event.type == KeyEventType.KeyDown && event.key == toDetailKey) {
allowDetailAutofocus = true
false
} else {
@ -343,8 +345,9 @@ fun SettingsScreen(
.weight(1f)
.fillMaxHeight()
.onKeyEvent { event ->
if (event.type == KeyEventType.KeyDown && event.key == Key.DirectionLeft) {
val movedLeft = focusManager.moveFocus(FocusDirection.Left)
val toRailKey = if (isRtl) Key.DirectionRight else Key.DirectionLeft
if (event.type == KeyEventType.KeyDown && event.key == toRailKey) {
val movedLeft = focusManager.moveFocus(if (isRtl) FocusDirection.Right else FocusDirection.Left)
if (!movedLeft) {
allowDetailAutofocus = false
val requested = railFocusRequesters[selectedCategory]?.let { requester ->

View file

@ -494,6 +494,7 @@ private fun RightStreamSection(
onRetry: () -> Unit,
modifier: Modifier = Modifier
) {
val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
var enter by remember { mutableStateOf(false) }
var shouldFocusFirstStream by remember { mutableStateOf(false) }
var wasLoading by remember { mutableStateOf(true) }
@ -620,14 +621,27 @@ private fun AddonFilterChips(
focusRequesters: List<FocusRequester>,
orderedNames: List<String>
) {
val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
val chipMap = sourceChips.associateBy { it.name }
var chipRowHasFocus by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val lastKeyRepeatDispatchRef = remember { java.util.concurrent.atomic.AtomicLong(0L) }
LazyRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp),
modifier = Modifier
.onFocusChanged { chipRowHasFocus = it.hasFocus }
.onFocusChanged { focusState ->
val hasFocus = focusState.hasFocus
if (hasFocus && !chipRowHasFocus && isRtl) {
val selectedIdx = if (selectedAddon == null) 0
else (orderedNames.indexOf(selectedAddon) + 1).coerceAtLeast(0)
scope.coroutineLaunch {
withFrameNanos {}
focusRequesters.getOrNull(selectedIdx)?.requestFocus()
}
}
chipRowHasFocus = hasFocus
}
.onKeyEvent { event ->
if (event.nativeKeyEvent.action != android.view.KeyEvent.ACTION_DOWN) return@onKeyEvent false
@ -642,10 +656,18 @@ private fun AddonFilterChips(
val currentIdx = allOptions.indexOf(selectedAddon)
when (event.key) {
androidx.compose.ui.input.key.Key.DirectionLeft -> {
if (currentIdx > 0) { onAddonSelected(allOptions[currentIdx - 1]); true } else false
if (isRtl) {
if (currentIdx < allOptions.lastIndex) { onAddonSelected(allOptions[currentIdx + 1]); true } else false
} else {
if (currentIdx > 0) { onAddonSelected(allOptions[currentIdx - 1]); true } else false
}
}
androidx.compose.ui.input.key.Key.DirectionRight -> {
if (currentIdx < allOptions.lastIndex) { onAddonSelected(allOptions[currentIdx + 1]); true } else false
if (isRtl) {
if (currentIdx > 0) { onAddonSelected(allOptions[currentIdx - 1]); true } else false
} else {
if (currentIdx < allOptions.lastIndex) { onAddonSelected(allOptions[currentIdx + 1]); true } else false
}
}
else -> false
}
@ -783,6 +805,7 @@ private fun StreamsList(
orderedAddonNames: List<String> = emptyList(),
onFocusChanged: (Boolean) -> Unit = {}
) {
val isRtl = androidx.compose.ui.platform.LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Rtl
val firstCardFocusRequester = remember { FocusRequester() }
val lastKeyRepeatDispatchRef = remember { java.util.concurrent.atomic.AtomicLong(0L) }
val restoreFocusRequester = remember { FocusRequester() }
@ -833,10 +856,18 @@ private fun StreamsList(
val currentIdx = allOptions.indexOf(selectedAddonFilter)
when (event.key) {
Key.DirectionLeft -> {
if (currentIdx > 0) { onAddonFilterSelected(allOptions[currentIdx - 1]); true } else false
if (isRtl) {
if (currentIdx < allOptions.lastIndex) { onAddonFilterSelected(allOptions[currentIdx + 1]); true } else false
} else {
if (currentIdx > 0) { onAddonFilterSelected(allOptions[currentIdx - 1]); true } else false
}
}
Key.DirectionRight -> {
if (currentIdx < allOptions.lastIndex) { onAddonFilterSelected(allOptions[currentIdx + 1]); true } else false
if (isRtl) {
if (currentIdx > 0) { onAddonFilterSelected(allOptions[currentIdx - 1]); true } else false
} else {
if (currentIdx < allOptions.lastIndex) { onAddonFilterSelected(allOptions[currentIdx + 1]); true } else false
}
}
else -> false
}