diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt index a5526f87..5d5c8923 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt @@ -5,12 +5,15 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.buildAnnotatedString +import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.WholphinApplication import com.github.damontecres.wholphin.ui.abbreviateNumber import com.github.damontecres.wholphin.ui.detail.CardGridItem import com.github.damontecres.wholphin.ui.detail.music.artistsString import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds import com.github.damontecres.wholphin.ui.dot import com.github.damontecres.wholphin.ui.formatDateTime +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.getDateFormatter import com.github.damontecres.wholphin.ui.joinNotBlank import com.github.damontecres.wholphin.ui.nav.Destination @@ -161,10 +164,18 @@ data class BaseItem( ?.ticks ?.roundMinutes ?.takeIf { it > Duration.ZERO } - ?.let { add(it.toString()) } + ?.let { add(WholphinApplication.instance.resources.formatDuration(it)) } data.timeRemaining ?.roundMinutes - ?.let { add("$it left") } + ?.let { + val resources = WholphinApplication.instance.resources + add( + resources.getString( + R.string.time_left, + resources.formatDuration(it), + ), + ) + } } details.forEachIndexed { index, string -> append(string) 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 2ea1bf87..a9ccd690 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,6 +1,7 @@ package com.github.damontecres.wholphin.ui import android.content.Context +import android.content.res.Resources import android.text.format.DateFormat import androidx.annotation.StringRes import androidx.compose.foundation.text.appendInlineContent @@ -21,6 +22,7 @@ import java.time.format.DateTimeParseException import java.time.format.FormatStyle import java.util.Date import java.util.Locale +import kotlin.time.Duration /** * Format the time-of-day of [time] honouring the device's 12/24-hour setting and the current @@ -259,3 +261,18 @@ fun formatTypeName(type: BaseItemKind): Int = BaseItemKind.LIVE_TV_PROGRAM -> TODO() BaseItemKind.PLAYLISTS_FOLDER -> TODO() } + +/** + * Format a [Duration] into a localized "Xh Ym Zs" string using string resources. + */ +fun Resources.formatDuration(duration: Duration): String = + duration.toComponents { hours, minutes, seconds, _ -> + when { + hours > 0 && minutes > 0 && seconds > 0 -> getString(R.string.duration_hours_minutes_seconds, hours.toInt(), minutes, seconds) + hours > 0 && minutes > 0 -> getString(R.string.duration_hours_minutes, hours.toInt(), minutes) + hours > 0 -> getString(R.string.duration_hours, hours.toInt()) + minutes > 0 && seconds > 0 -> getString(R.string.duration_minutes_seconds, minutes, seconds) + minutes > 0 -> getString(R.string.duration_minutes, minutes) + else -> getString(R.string.duration_seconds, seconds) + } + } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterCard.kt index 54d1d2fc..d3c87124 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterCard.kt @@ -9,15 +9,18 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.tv.material3.Card import androidx.tv.material3.Text import coil3.compose.AsyncImage import com.github.damontecres.wholphin.ui.AppColors +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.roundSeconds import kotlin.time.Duration @@ -62,8 +65,10 @@ fun ChapterCard( text = it, ) } + val resources = LocalResources.current + val positionText = remember(position) { resources.formatDuration(position.roundSeconds) } Text( - text = position.roundSeconds.toString(), + text = positionText, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderList.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderList.kt index 3c69e5ce..e82d87a5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderList.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderList.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.tv.material3.ListItemDefaults @@ -58,6 +59,7 @@ import com.github.damontecres.wholphin.ui.cards.WatchedIcon import com.github.damontecres.wholphin.ui.data.SortAndDirection import com.github.damontecres.wholphin.ui.detail.AlphabetButtons import com.github.damontecres.wholphin.ui.enableMarquee +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.rememberInt @@ -357,7 +359,7 @@ fun ListItemGeneric( ?.runTimeTicks ?.ticks ?.roundMinutes - ?.toString() ?: "", + ?.let { LocalResources.current.formatDuration(it) } ?: "", ) }, scale = scale, @@ -419,7 +421,7 @@ fun ListItemMovie( ?.runTimeTicks ?.ticks ?.roundMinutes - ?.toString() ?: "", + ?.let { LocalResources.current.formatDuration(it) } ?: "", ) if (item?.played == true) { WatchedIcon(padding = 0.dp) @@ -546,7 +548,7 @@ fun ListItemAlbum( .runTimeTicks ?.ticks ?.roundMinutes - ?.toString() ?: "", + ?.let { LocalResources.current.formatDuration(it) } ?: "", ) } } else { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/data/ItemDetailsDialogInfo.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/data/ItemDetailsDialogInfo.kt index f0591868..08bd0c55 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/data/ItemDetailsDialogInfo.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/data/ItemDetailsDialogInfo.kt @@ -25,6 +25,7 @@ import com.github.damontecres.wholphin.ui.components.ScrollableDialog import com.github.damontecres.wholphin.ui.formatBitrate import com.github.damontecres.wholphin.ui.formatBytes import com.github.damontecres.wholphin.ui.formatDate +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.util.StreamFormatting.formatAudioCodec @@ -153,7 +154,7 @@ fun ItemDetailsDialog( ) } source.runTimeTicks?.let { - add(runtimeLabel to it.ticks.toString()) + add(runtimeLabel to resources.formatDuration(it.ticks)) } }, ) 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 ea1640ed..7d66c08b 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 @@ -42,6 +42,7 @@ import androidx.compose.ui.focus.focusRestorer import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -94,6 +95,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.formatDuration import com.github.damontecres.wholphin.ui.formatTime import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.launchDefault @@ -809,9 +811,11 @@ fun PlaylistItem( val endTime = now.toLocalTime().plusSeconds(duration.inWholeSeconds) formatTime(context, endTime) } + val resources = LocalResources.current + val durationText = remember(duration) { resources.formatDuration(duration) } Column { Text( - text = duration.toString(), + text = durationText, ) if (item.type != BaseItemKind.AUDIO) { Text( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt index 60d3806b..8b6f3e98 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextOverflow @@ -25,6 +26,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.ui.components.GenreText import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.QuickDetailsText +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.listToDotString @@ -64,16 +66,16 @@ fun DiscoverMovieDetailsHeader( modifier = Modifier.fillMaxWidth(.60f), ) { val padding = 4.dp + val resources = LocalResources.current val details = - remember(movie, rating) { + remember(movie, rating, resources) { buildList { movie.releaseDate?.let(::add) movie.runtime ?.toDouble() ?.minutes ?.roundMinutes - ?.toString() - ?.let(::add) + ?.let { add(resources.formatDuration(it)) } val release = movie.releases ?.results diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt index be9acc64..947dae96 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt @@ -58,6 +58,7 @@ import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.QuickDetailsText import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.listToDotString import com.github.damontecres.wholphin.ui.nav.Destination @@ -446,8 +447,9 @@ fun DiscoverSeriesDetailsHeader( modifier = Modifier.fillMaxWidth(.60f), ) { val padding = 4.dp + val resources = LocalResources.current val details = - remember(series, rating) { + remember(series, rating, resources) { buildList { series.firstAirDate?.let(::add) series.episodeRunTime @@ -455,8 +457,7 @@ fun DiscoverSeriesDetailsHeader( ?.takeIf { !it.isNaN() && it > 0 } ?.minutes ?.roundMinutes - ?.toString() - ?.let(::add) + ?.let { add(resources.formatDuration(it)) } // TODO }.let { listToDotString( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/livetv/LiveTvViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/livetv/LiveTvViewModel.kt index 01279c97..a2a4bd01 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/livetv/LiveTvViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/livetv/LiveTvViewModel.kt @@ -20,6 +20,7 @@ import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode import com.github.damontecres.wholphin.ui.dot +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO @@ -643,18 +644,16 @@ data class TvProgram( append(time) if (!isFake) { + val resources = WholphinApplication.instance.resources dot() - duration - .roundMinutes - .toString() - .let(::append) + append(resources.formatDuration(duration.roundMinutes)) if (now.isAfter(start) && now.isBefore(end)) { dot() java.time.Duration .between(now, end) .toKotlinDuration() .roundMinutes - .let { append("$it left") } + .let { append(resources.getString(R.string.time_left, resources.formatDuration(it))) } } seasonEpisode?.let { "S${it.season} E${it.episode}" }?.let { dot() diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/SongListDisplay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/SongListDisplay.kt index 011bb8ea..3879cca4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/SongListDisplay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/music/SongListDisplay.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.tv.material3.DenseListItem @@ -32,6 +33,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.components.Button import com.github.damontecres.wholphin.ui.enableMarquee +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.roundSeconds import com.github.damontecres.wholphin.ui.theme.WholphinTheme @@ -115,7 +117,7 @@ fun SongListItem( } val trailingContent = @Composable { Text( - text = runtime.toString(), + text = runtime?.let { LocalResources.current.formatDuration(it) } ?: "", ) } 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 86cd81b1..2909e666 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 @@ -215,7 +215,7 @@ fun Controller( } } Text( - text = "Ends $endTimeStr", + text = stringResource(R.string.ends_at, endTimeStr), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge, modifier = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackControls.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackControls.kt index 820e634f..79f44929 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackControls.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackControls.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -70,6 +71,7 @@ import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.components.Button import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent import com.github.damontecres.wholphin.ui.components.TextButton +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.indexOfFirstOrNull import com.github.damontecres.wholphin.ui.playback.ControllerViewState import com.github.damontecres.wholphin.ui.playback.PlaybackDialogType @@ -274,9 +276,13 @@ fun SeekBar( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - val remaining = ((player.duration - position) / 1000).seconds + val resources = LocalResources.current + val positionSec = position / 1000 + val remainingSec = (player.duration - position) / 1000 + val positionText = remember(positionSec) { resources.formatDuration(positionSec.seconds) } + val remainingText = remember(remainingSec) { "-${resources.formatDuration(remainingSec.seconds)}" } Text( - text = (position / 1000).seconds.toString(), + text = positionText, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge, modifier = @@ -284,7 +290,7 @@ fun SeekBar( .padding(8.dp), ) Text( - text = "-$remaining", + text = remainingText, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge, modifier = diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackOverlay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackOverlay.kt index 2c2aefe8..a6ade035 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackOverlay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/PlaybackOverlay.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp @@ -56,6 +57,7 @@ import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.LocalImageUrlService import com.github.damontecres.wholphin.ui.components.TimeDisplay +import com.github.damontecres.wholphin.ui.formatDuration import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.playback.AnalyticsState import com.github.damontecres.wholphin.ui.playback.ControllerViewState @@ -304,8 +306,10 @@ fun PlaybackOverlay( ) } } + val resources = LocalResources.current + val seekText = remember(seekProgressMs / 1000L) { resources.formatDuration((seekProgressMs / 1000L).seconds) } Text( - text = (seekProgressMs / 1000L).seconds.toString(), + text = seekText, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge, modifier = diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 312f78b2..0af5ea00 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,12 @@ Continue watching Critic Rating %.2f seconds + %1$dh %2$dm %3$ds + %1$dh %2$dm + %1$dm %2$ds + %1$dh + %1$dm + %1$ds Default Delete Died @@ -144,6 +150,7 @@ Suggestions Switch servers Switch + %1$s left Top Rated Unwatched Trailers DVR Schedule