From 68387cfe41e0e1c5fc715fe3a1b5ad059dee4705 Mon Sep 17 00:00:00 2001 From: Robert Stone Date: Fri, 7 Nov 2025 10:42:17 -0800 Subject: [PATCH] Correctly display fractions in right-to-left mode --- .../main/java/org/videolan/tools/Strings.kt | 22 +++++++++++------ .../org/videolan/vlc/gui/audio/AudioPlayer.kt | 24 +++++++++++++++---- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/application/tools/src/main/java/org/videolan/tools/Strings.kt b/application/tools/src/main/java/org/videolan/tools/Strings.kt index a8644d85a..9c4747aa9 100644 --- a/application/tools/src/main/java/org/videolan/tools/Strings.kt +++ b/application/tools/src/main/java/org/videolan/tools/Strings.kt @@ -120,21 +120,29 @@ fun Long.readableNumber(): String { fun Int.forbiddenChars() = FORBIDDEN_CHARS.substrlng(this) -fun String.markBidi(): String { +fun String.markBidi(markLtr: Boolean = false): String { + //left-to-right isolate + val lri = "\u2066" //right-to-left isolate val rli = "\u2067" //pop directional isolate val pdi = "\u2069" - for (ch in this) { + return when { + markLtr -> lri + this + pdi + this.hasRtl() -> rli + this + pdi + else -> this + } +} + +fun String.hasRtl(): Boolean { + return this.toCharArray().any { ch -> when (Character.getDirectionality(ch)) { Character.DIRECTIONALITY_RIGHT_TO_LEFT, Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC, Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING, - Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE -> return rli + this + pdi - Character.DIRECTIONALITY_LEFT_TO_RIGHT, - Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING, - Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE -> return this + Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE, + Character.DIRECTIONALITY_RIGHT_TO_LEFT_ISOLATE -> return true + else -> false } } - return this } \ No newline at end of file diff --git a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt index 6a0a05679..edd7322e5 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt @@ -87,7 +87,9 @@ import org.videolan.tools.Settings import org.videolan.tools.copy import org.videolan.tools.dp import org.videolan.tools.formatRateString +import org.videolan.tools.hasRtl import org.videolan.tools.isStarted +import org.videolan.tools.markBidi import org.videolan.tools.putSingle import org.videolan.tools.setGone import org.videolan.tools.setVisible @@ -647,8 +649,11 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay val progressTimeDescription = TalkbackUtil.millisToString(requireActivity(), if (showRemainingTime && totalTime > 0) totalTime - progressTime else progressTime) val currentProgressText = if (progressTimeText.isNullOrEmpty()) "0:00" else progressTimeText + val isRtlLocale = LocaleUtil.isRtl() val size = if (playlistModel.service?.playlistManager?.stopAfter != -1 ) (playlistModel.service?.playlistManager?.stopAfter ?: 0) + 1 else medias.size - val textTrack = getString(R.string.track_index, "${playlistModel.currentMediaPosition + 1} / $size") + val textTrack = getString(R.string.track_index, "${playlistModel.currentMediaPosition + 1} / $size".let { + if (isRtlLocale) it.markBidi(true) else it + }) val textTrackDescription = getString(R.string.talkback_track_index, "${playlistModel.currentMediaPosition + 1}", "$size") val textProgress = if (audioPlayProgressMode) { @@ -656,7 +661,9 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay if ((lastEndsAt - endsAt).absoluteValue > 1) lastEndsAt = endsAt getString( R.string.audio_queue_progress_finished, - getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt) + getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt).let { + if (isRtlLocale) it.markBidi(true) else it + } ) } else if (showRemainingTime && totalTime > 0) getString( @@ -665,14 +672,18 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay ) else getString( R.string.audio_queue_progress, - if (totalTimeText.isNullOrEmpty()) currentProgressText else "$currentProgressText / $totalTimeText" + if (totalTimeText.isNullOrEmpty()) currentProgressText else "$currentProgressText / $totalTimeText".let { + if (isRtlLocale) it.markBidi(true) else it + } ) val textDescription = if (audioPlayProgressMode) { val endsAt = System.currentTimeMillis() + totalTime - progressTime if ((lastEndsAt - endsAt).absoluteValue > 1) lastEndsAt = endsAt getString( R.string.audio_queue_progress_finished, - getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt) + getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt).let { + if (isRtlLocale) it.markBidi(true) else it + } ) } else if (showRemainingTime && totalTime > 0) getString( @@ -683,7 +694,10 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay R.string.audio_queue_progress, if (totalTimeText.isNullOrEmpty()) progressTimeDescription else getString(R.string.talkback_out_of, progressTimeDescription, totalTimeDescription) ) - Pair("$textTrack ${TextUtils.SEPARATOR} $textProgress", "$textTrackDescription. $textDescription") + + val finalTextTrack = if (isRtlLocale && !textTrack.hasRtl()) textTrack.markBidi(true) else textTrack + val finalTextProgress = if (isRtlLocale && !textProgress.hasRtl()) textProgress.markBidi(true) else textProgress + Pair("$finalTextTrack ${TextUtils.SEPARATOR} $finalTextProgress", "$textTrackDescription. $textDescription") } binding.audioPlayProgress.text = text.first binding.audioPlayProgress.contentDescription = text.second