Respect system 24-hour setting in clock time format (#1619)
## Description <!-- Describe the changes in detail --> 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 <!-- If this is a new feature or a change, there must be a discussion in an issue first, reference the issue here --> <!-- If fixing a bug, reference the bug issue here, or describe the bug, including steps to reproduce --> ### Testing <!-- Describe how this change was tested and on what device(s) --> Tested on the emulator toggling the system 24-hour setting on/off. assembleDefaultDebug, testDefaultDebugUnitTest and pre-commit (ktlint) pass. ## Screenshots <!-- Please include screenshots if the PR alters any UI elements --> ## AI or LLM usage <!-- If you used any AI or LLM assistance, please list where in the code and how you tested it --> 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.
This commit is contained in:
parent
d705a3ea01
commit
8a684545ac
5 changed files with 49 additions and 34 deletions
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<LocalDateTime> = 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<String> = mutableStateOf(getTimeFormatter().format(now.value)),
|
||||
val timeString: MutableState<String> = 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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue