From 8a684545ac6ca2f16d0089c2a7ac91d80458d752 Mon Sep 17 00:00:00 2001 From: Christian Tismer-Sperling Date: Wed, 24 Jun 2026 23:02:53 +0200 Subject: [PATCH] Respect system 24-hour setting in clock time format (#1619) ## Description The clock ignored the device's system 24-hour preference: getTimeFormatter derived 12/24h purely from the locale. This reads DateFormat.is24HourFormat and builds the time pattern from it, so the clock follows the system setting. ### Related issues ### Testing Tested on the emulator toggling the system 24-hour setting on/off. assembleDefaultDebug, testDefaultDebugUnitTest and pre-commit (ktlint) pass. ## Screenshots ## AI or LLM usage The analysis and the fix were done by me with AI assistance (Claude). I wrote the description myself; the English wording was polished with AI. I understand the code and can maintain it. --- .../damontecres/wholphin/ui/Formatting.kt | 32 +++++++++++++++---- .../wholphin/ui/components/QuickDetails.kt | 24 +++++--------- .../wholphin/ui/detail/PlaylistDetails.kt | 7 ++-- .../ui/playback/overlay/PlaybackController.kt | 8 +++-- .../wholphin/ui/util/LocalClock.kt | 12 ++++--- 5 files changed, 49 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt index 9ae6cd50..2ea1bf87 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Formatting.kt @@ -1,5 +1,7 @@ package com.github.damontecres.wholphin.ui +import android.content.Context +import android.text.format.DateFormat import androidx.annotation.StringRes import androidx.compose.foundation.text.appendInlineContent import androidx.compose.ui.text.AnnotatedString @@ -12,19 +14,35 @@ import org.jellyfin.sdk.model.api.MediaSegmentType import timber.log.Timber import java.time.LocalDate import java.time.LocalDateTime +import java.time.LocalTime +import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.format.DateTimeParseException import java.time.format.FormatStyle +import java.util.Date import java.util.Locale -private var timeFormatter: DateTimeFormatter = - DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.getDefault()) +/** + * Format the time-of-day of [time] honouring the device's 12/24-hour setting and the current + * locale. [context] should be an Activity/composition context (e.g. `LocalContext.current`) so an + * in-app language switch (`AppCompatDelegate.setApplicationLocales`) is reflected; the application + * context may carry a stale locale. + */ +fun formatTime( + context: Context, + time: LocalDateTime, +): String = formatTime(context, time.toLocalTime()) -fun getTimeFormatter(): DateTimeFormatter { - if (timeFormatter.locale != Locale.getDefault()) { - timeFormatter = timeFormatter.withLocale(Locale.getDefault()) - } - return timeFormatter +fun formatTime( + context: Context, + time: LocalTime, +): String { + val instant = + time + .atDate(LocalDate.now()) + .atZone(ZoneId.systemDefault()) + .toInstant() + return DateFormat.getTimeFormat(context).format(Date.from(instant)) } private var dateFormatter: DateTimeFormatter = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/QuickDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/QuickDetails.kt index e9f34133..6db89c75 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/QuickDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/QuickDetails.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.AnnotatedString @@ -25,7 +26,7 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.QuickDetailsData import com.github.damontecres.wholphin.preferences.DisplayToggle import com.github.damontecres.wholphin.ui.dot -import com.github.damontecres.wholphin.ui.getTimeFormatter +import com.github.damontecres.wholphin.ui.formatTime import com.github.damontecres.wholphin.ui.util.LocalClock import com.github.damontecres.wholphin.ui.util.LocalInterfaceCustomization import org.jellyfin.sdk.model.DateTime @@ -138,21 +139,11 @@ fun TimeRemaining( modifier: Modifier = Modifier, textStyle: TextStyle = MaterialTheme.typography.titleSmall, ) { - val resources = LocalResources.current val now by LocalClock.current.now - val remainingStr = - remember(remaining, now, resources) { - val endTimeStr = getTimeFormatter().format(now.plusSeconds(remaining.inWholeSeconds)) - buildAnnotatedString { - dot() - append(resources.getString(R.string.ends_at, endTimeStr)) - } - } - Text( - text = remainingStr, - style = textStyle, - maxLines = 1, + EndsAt( + endsAt = now.plusSeconds(remaining.inWholeSeconds), modifier = modifier, + textStyle = textStyle, ) } @@ -163,9 +154,10 @@ fun EndsAt( textStyle: TextStyle = MaterialTheme.typography.titleSmall, ) { val resources = LocalResources.current + val context = LocalContext.current val remainingStr = - remember(endsAt, resources) { - val endTimeStr = getTimeFormatter().format(endsAt) + remember(endsAt, resources, context) { + val endTimeStr = formatTime(context, endsAt) buildAnnotatedString { dot() append(resources.getString(R.string.ends_at, endTimeStr)) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt index 070268cb..ea1640ed 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/PlaylistDetails.kt @@ -94,7 +94,7 @@ import com.github.damontecres.wholphin.ui.detail.music.MusicViewModel import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.equalsNotNull import com.github.damontecres.wholphin.ui.formatDateTime -import com.github.damontecres.wholphin.ui.getTimeFormatter +import com.github.damontecres.wholphin.ui.formatTime import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO @@ -803,10 +803,11 @@ fun PlaylistItem( trailingContent = { item?.data?.runTimeTicks?.ticks?.roundMinutes?.let { duration -> val now by LocalClock.current.now + val context = LocalContext.current val endTimeStr = - remember(item, now) { + remember(item, now, context) { val endTime = now.toLocalTime().plusSeconds(duration.inWholeSeconds) - getTimeFormatter().format(endTime) + formatTime(context, endTime) } Column { Text( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackController.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackController.kt index 528b6546..86cd81b1 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackController.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackController.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp @@ -32,7 +33,7 @@ import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.BaseItem -import com.github.damontecres.wholphin.ui.getTimeFormatter +import com.github.damontecres.wholphin.ui.formatTime import com.github.damontecres.wholphin.ui.playback.ControllerViewState import com.github.damontecres.wholphin.ui.playback.PlaybackDialogType import kotlinx.coroutines.delay @@ -199,8 +200,9 @@ fun Controller( ) } + val context = LocalContext.current var endTimeStr by remember { mutableStateOf("...") } - LaunchedEffect(player) { + LaunchedEffect(player, context) { while (isActive) { val remaining = (player.duration - player.currentPosition) @@ -208,7 +210,7 @@ fun Controller( .toLong() .milliseconds val endTime = LocalTime.now().plusSeconds(remaining.inWholeSeconds) - endTimeStr = getTimeFormatter().format(endTime) + endTimeStr = formatTime(context, endTime) delay(1.seconds) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/util/LocalClock.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/util/LocalClock.kt index 8664d604..2639a919 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/util/LocalClock.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/util/LocalClock.kt @@ -7,7 +7,8 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import com.github.damontecres.wholphin.ui.getTimeFormatter +import androidx.compose.ui.platform.LocalContext +import com.github.damontecres.wholphin.ui.formatTime import com.github.damontecres.wholphin.util.WholphinDispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.isActive @@ -25,19 +26,20 @@ data class Clock( */ val now: MutableState = mutableStateOf(LocalDateTime.now()), /** - * The current time formatted as a string with [getTimeFormatter] + * The current time formatted as a string; populated by [ProvideLocalClock] via [formatTime]. */ - val timeString: MutableState = mutableStateOf(getTimeFormatter().format(now.value)), + val timeString: MutableState = mutableStateOf(""), ) @Composable fun ProvideLocalClock(content: @Composable () -> Unit) { + val context = LocalContext.current val clock = remember { Clock() } - LaunchedEffect(Unit) { + LaunchedEffect(context) { withContext(WholphinDispatchers.Default) { while (isActive) { val now = LocalDateTime.now() - val time = getTimeFormatter().format(now) + val time = formatTime(context, now) clock.now.value = now clock.timeString.value = time delay(2_000)