feat: add runtime formatting

This commit is contained in:
tapframe 2026-04-12 19:59:54 +05:30
parent 2f74c3cab0
commit 81cd7933eb
4 changed files with 52 additions and 10 deletions

View file

@ -0,0 +1,46 @@
package com.nuvio.app.features.details
private val hourTokenRegex = Regex("""(?i)(\d+)\s*h(?:ours?)?""")
private val minuteTokenRegex = Regex("""(?i)(\d+)\s*m(?:in(?:ute)?s?)?""")
private val hourMinuteColonRegex = Regex("""^\s*(\d+)\s*:\s*(\d{1,2})\s*$""")
private val digitsOnlyRegex = Regex("""^\s*(\d+)\s*$""")
internal fun formatRuntimeForDisplay(rawRuntime: String?): String? {
val normalized = rawRuntime?.trim()?.takeIf { it.isNotBlank() } ?: return null
val totalMinutes = parseRuntimeMinutes(normalized) ?: return normalized
return formatRuntimeFromMinutes(totalMinutes)
}
internal fun formatRuntimeFromMinutes(totalMinutes: Int): String {
if (totalMinutes <= 0) return ""
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
return when {
hours > 0 && minutes > 0 -> "${hours}h ${minutes}m"
hours > 0 -> "${hours}h"
else -> "${minutes}m"
}
}
private fun parseRuntimeMinutes(value: String): Int? {
hourMinuteColonRegex.matchEntire(value)?.let { match ->
val hours = match.groupValues[1].toIntOrNull() ?: return null
val minutes = match.groupValues[2].toIntOrNull() ?: return null
return (hours * 60) + minutes
}
val hoursToken = hourTokenRegex.find(value)?.groupValues?.getOrNull(1)?.toIntOrNull()
val minutesToken = minuteTokenRegex.find(value)?.groupValues?.getOrNull(1)?.toIntOrNull()
if (hoursToken != null || minutesToken != null) {
val hours = (hoursToken ?: 0).coerceAtLeast(0)
val minutes = (minutesToken ?: 0).coerceAtLeast(0)
return (hours * 60) + minutes
}
digitsOnlyRegex.matchEntire(value)?.let { match ->
return match.groupValues[1].toIntOrNull()?.coerceAtLeast(0)
}
return null
}

View file

@ -18,6 +18,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.nuvio.app.core.format.formatReleaseDateForDisplay
import com.nuvio.app.features.details.MetaDetails
import com.nuvio.app.features.details.formatRuntimeForDisplay
@Composable
fun DetailAdditionalInfoSection(
@ -30,7 +31,7 @@ fun DetailAdditionalInfoSection(
val rows = buildList {
meta.status?.let { add("Status" to it) }
meta.releaseInfo?.let { add("Release Info" to formatReleaseDateForDisplay(it)) }
meta.runtime?.let { add("Runtime" to it.uppercase()) }
formatRuntimeForDisplay(meta.runtime)?.let { add("Runtime" to it) }
meta.ageRating?.let { add("Certification" to it) }
meta.country?.let { add("Origin Country" to it) }
meta.language?.let { add("Original Language" to it.uppercase()) }

View file

@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.nuvio.app.features.details.MetaDetails
import com.nuvio.app.features.details.MetaExternalRating
import com.nuvio.app.features.details.formatRuntimeForDisplay
import com.nuvio.app.features.details.formatMetaReleaseLineForDetails
import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_AUDIENCE
import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_IMDB
@ -73,7 +74,7 @@ fun DetailMetaInfo(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
val releaseLine = formatMetaReleaseLineForDetails(meta)
val runtimeText = meta.runtime?.trim()?.takeIf { it.isNotBlank() }?.uppercase()
val runtimeText = formatRuntimeForDisplay(meta.runtime)
val ageBadge = meta.ageRating?.trim()?.takeIf { it.isNotBlank() }
val hasMdbImdbRating = meta.externalRatings.any { it.source == PROVIDER_IMDB }
val hasMetaRow = releaseLine != null ||

View file

@ -64,6 +64,7 @@ import com.nuvio.app.features.details.MetaEpisodeCardStyle
import com.nuvio.app.features.details.MetaVideo
import com.nuvio.app.features.details.SeasonViewMode
import com.nuvio.app.features.details.SeasonViewModeStorage
import com.nuvio.app.features.details.formatRuntimeFromMinutes
import com.nuvio.app.features.details.metaVideoSeasonEpisodeComparator
import com.nuvio.app.features.details.normalizeSeasonNumber
import com.nuvio.app.features.details.seasonSortKey
@ -843,14 +844,7 @@ private fun rememberEpisodeHorizontalCardMetrics(maxWidthDp: Float): EpisodeHori
}
private fun formatEpisodeRuntime(runtimeMinutes: Int): String {
if (runtimeMinutes <= 0) return ""
val hours = runtimeMinutes / 60
val minutes = runtimeMinutes % 60
return when {
hours > 0 && minutes > 0 -> "${hours}h ${minutes}m"
hours > 0 -> "${hours}h"
else -> "${minutes}m"
}
return formatRuntimeFromMinutes(runtimeMinutes)
}
@OptIn(ExperimentalFoundationApi::class)