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
|
package com.github.damontecres.wholphin.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.DateFormat
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.text.appendInlineContent
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
|
@ -12,19 +14,35 @@ import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import java.time.LocalTime
|
||||||
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.format.DateTimeParseException
|
import java.time.format.DateTimeParseException
|
||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
|
import java.util.Date
|
||||||
import java.util.Locale
|
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 {
|
fun formatTime(
|
||||||
if (timeFormatter.locale != Locale.getDefault()) {
|
context: Context,
|
||||||
timeFormatter = timeFormatter.withLocale(Locale.getDefault())
|
time: LocalTime,
|
||||||
}
|
): String {
|
||||||
return timeFormatter
|
val instant =
|
||||||
|
time
|
||||||
|
.atDate(LocalDate.now())
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
.toInstant()
|
||||||
|
return DateFormat.getTimeFormat(context).format(Date.from(instant))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dateFormatter: DateTimeFormatter =
|
private var dateFormatter: DateTimeFormatter =
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalResources
|
import androidx.compose.ui.platform.LocalResources
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
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.data.model.QuickDetailsData
|
||||||
import com.github.damontecres.wholphin.preferences.DisplayToggle
|
import com.github.damontecres.wholphin.preferences.DisplayToggle
|
||||||
import com.github.damontecres.wholphin.ui.dot
|
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.LocalClock
|
||||||
import com.github.damontecres.wholphin.ui.util.LocalInterfaceCustomization
|
import com.github.damontecres.wholphin.ui.util.LocalInterfaceCustomization
|
||||||
import org.jellyfin.sdk.model.DateTime
|
import org.jellyfin.sdk.model.DateTime
|
||||||
|
|
@ -138,21 +139,11 @@ fun TimeRemaining(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
textStyle: TextStyle = MaterialTheme.typography.titleSmall,
|
textStyle: TextStyle = MaterialTheme.typography.titleSmall,
|
||||||
) {
|
) {
|
||||||
val resources = LocalResources.current
|
|
||||||
val now by LocalClock.current.now
|
val now by LocalClock.current.now
|
||||||
val remainingStr =
|
EndsAt(
|
||||||
remember(remaining, now, resources) {
|
endsAt = now.plusSeconds(remaining.inWholeSeconds),
|
||||||
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,
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
textStyle = textStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,9 +154,10 @@ fun EndsAt(
|
||||||
textStyle: TextStyle = MaterialTheme.typography.titleSmall,
|
textStyle: TextStyle = MaterialTheme.typography.titleSmall,
|
||||||
) {
|
) {
|
||||||
val resources = LocalResources.current
|
val resources = LocalResources.current
|
||||||
|
val context = LocalContext.current
|
||||||
val remainingStr =
|
val remainingStr =
|
||||||
remember(endsAt, resources) {
|
remember(endsAt, resources, context) {
|
||||||
val endTimeStr = getTimeFormatter().format(endsAt)
|
val endTimeStr = formatTime(context, endsAt)
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
dot()
|
dot()
|
||||||
append(resources.getString(R.string.ends_at, endTimeStr))
|
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.enableMarquee
|
||||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
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.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
|
@ -803,10 +803,11 @@ fun PlaylistItem(
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
item?.data?.runTimeTicks?.ticks?.roundMinutes?.let { duration ->
|
item?.data?.runTimeTicks?.ticks?.roundMinutes?.let { duration ->
|
||||||
val now by LocalClock.current.now
|
val now by LocalClock.current.now
|
||||||
|
val context = LocalContext.current
|
||||||
val endTimeStr =
|
val endTimeStr =
|
||||||
remember(item, now) {
|
remember(item, now, context) {
|
||||||
val endTime = now.toLocalTime().plusSeconds(duration.inWholeSeconds)
|
val endTime = now.toLocalTime().plusSeconds(duration.inWholeSeconds)
|
||||||
getTimeFormatter().format(endTime)
|
formatTime(context, endTime)
|
||||||
}
|
}
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
@ -32,7 +33,7 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
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.ControllerViewState
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackDialogType
|
import com.github.damontecres.wholphin.ui.playback.PlaybackDialogType
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
@ -199,8 +200,9 @@ fun Controller(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
var endTimeStr by remember { mutableStateOf("...") }
|
var endTimeStr by remember { mutableStateOf("...") }
|
||||||
LaunchedEffect(player) {
|
LaunchedEffect(player, context) {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val remaining =
|
val remaining =
|
||||||
(player.duration - player.currentPosition)
|
(player.duration - player.currentPosition)
|
||||||
|
|
@ -208,7 +210,7 @@ fun Controller(
|
||||||
.toLong()
|
.toLong()
|
||||||
.milliseconds
|
.milliseconds
|
||||||
val endTime = LocalTime.now().plusSeconds(remaining.inWholeSeconds)
|
val endTime = LocalTime.now().plusSeconds(remaining.inWholeSeconds)
|
||||||
endTimeStr = getTimeFormatter().format(endTime)
|
endTimeStr = formatTime(context, endTime)
|
||||||
delay(1.seconds)
|
delay(1.seconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
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 com.github.damontecres.wholphin.util.WholphinDispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
|
|
@ -25,19 +26,20 @@ data class Clock(
|
||||||
*/
|
*/
|
||||||
val now: MutableState<LocalDateTime> = mutableStateOf(LocalDateTime.now()),
|
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
|
@Composable
|
||||||
fun ProvideLocalClock(content: @Composable () -> Unit) {
|
fun ProvideLocalClock(content: @Composable () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
val clock = remember { Clock() }
|
val clock = remember { Clock() }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(context) {
|
||||||
withContext(WholphinDispatchers.Default) {
|
withContext(WholphinDispatchers.Default) {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val time = getTimeFormatter().format(now)
|
val time = formatTime(context, now)
|
||||||
clock.now.value = now
|
clock.now.value = now
|
||||||
clock.timeString.value = time
|
clock.timeString.value = time
|
||||||
delay(2_000)
|
delay(2_000)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue