Add "Ends at" time display. (#475)

Addresses #441 .

---------

Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
Amar Sandhu 2025-12-18 13:52:27 -07:00 committed by GitHub
parent 1855c4666b
commit fd260f2709
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 24 deletions

View file

@ -1,29 +1,32 @@
package com.github.damontecres.wholphin.ui.components package com.github.damontecres.wholphin.ui.components
import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.TimeFormatter
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.timeRemaining import com.github.damontecres.wholphin.ui.timeRemaining
import com.github.damontecres.wholphin.ui.util.LocalClock
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.extensions.ticks import org.jellyfin.sdk.model.extensions.ticks
import java.time.LocalDateTime
@Composable @Composable
fun MovieQuickDetails( fun MovieQuickDetails(
dto: BaseItemDto?, dto: BaseItemDto?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val context = LocalContext.current
val now = LocalClock.current.now
val details = val details =
remember(dto) { remember(dto, now) {
buildList { buildList {
dto?.productionYear?.let { add(it.toString()) } dto?.productionYear?.let { add(it.toString()) }
dto?.runTimeTicks?.ticks?.roundMinutes?.let { addRuntimeDetails(context, now, dto)
add(it.toString())
}
dto?.timeRemaining?.roundMinutes?.let {
add("$it left")
}
dto?.officialRating?.let(::add) dto?.officialRating?.let(::add)
} }
} }
@ -36,3 +39,21 @@ fun MovieQuickDetails(
modifier = modifier, modifier = modifier,
) )
} }
fun MutableList<String>.addRuntimeDetails(
context: Context,
now: LocalDateTime,
dto: BaseItemDto?,
) {
val runtime = dto?.runTimeTicks?.ticks
runtime?.let { duration ->
add(duration.roundMinutes.toString())
}
dto?.timeRemaining?.roundMinutes?.let {
add("$it left")
}
(dto?.timeRemaining ?: runtime)?.let { remaining ->
val endTimeStr = TimeFormatter.format(now.plusSeconds(remaining.inWholeSeconds))
add(context.getString(R.string.ends_at, endTimeStr))
}
}

View file

@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
@ -11,7 +12,7 @@ import com.github.damontecres.wholphin.ui.formatDateTime
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.seasonEpisode import com.github.damontecres.wholphin.ui.seasonEpisode
import com.github.damontecres.wholphin.ui.seriesProductionYears import com.github.damontecres.wholphin.ui.seriesProductionYears
import com.github.damontecres.wholphin.ui.timeRemaining import com.github.damontecres.wholphin.ui.util.LocalClock
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.extensions.ticks import org.jellyfin.sdk.model.extensions.ticks
@ -57,17 +58,14 @@ fun EpisodeQuickDetails(
dto: BaseItemDto?, dto: BaseItemDto?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val context = LocalContext.current
val now = LocalClock.current.now
val details = val details =
remember(dto) { remember(dto, now) {
buildList { buildList {
dto?.seasonEpisode?.let(::add) dto?.seasonEpisode?.let(::add)
dto?.premiereDate?.let { add(formatDateTime(it)) } dto?.premiereDate?.let { add(formatDateTime(it)) }
val duration = dto?.runTimeTicks?.ticks addRuntimeDetails(context, now, dto)
duration
?.roundMinutes
?.toString()
?.let(::add)
dto?.timeRemaining?.roundMinutes?.let { add("$it left") }
dto?.officialRating?.let(::add) dto?.officialRating?.let(::add)
} }
} }
@ -89,11 +87,9 @@ fun SeriesQuickDetails(
remember(dto) { remember(dto) {
buildList { buildList {
dto?.seriesProductionYears?.let(::add) dto?.seriesProductionYears?.let(::add)
val duration = dto?.runTimeTicks?.ticks dto?.runTimeTicks?.ticks?.roundMinutes?.let {
duration add(it.toString())
?.roundMinutes }
?.toString()
?.let(::add)
dto?.officialRating?.let(::add) dto?.officialRating?.let(::add)
} }
} }

View file

@ -53,6 +53,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.TimeFormatter
import com.github.damontecres.wholphin.ui.cards.ItemCardImage import com.github.damontecres.wholphin.ui.cards.ItemCardImage
import com.github.damontecres.wholphin.ui.components.DialogItem import com.github.damontecres.wholphin.ui.components.DialogItem
import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogParams
@ -68,6 +69,7 @@ import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.LocalClock
import com.github.damontecres.wholphin.util.ApiRequestPager import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.GetPlaylistItemsRequestHandler import com.github.damontecres.wholphin.util.GetPlaylistItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler
@ -407,10 +409,22 @@ fun PlaylistItem(
) )
}, },
trailingContent = { trailingContent = {
item?.data?.runTimeTicks?.ticks?.roundMinutes?.let { item?.data?.runTimeTicks?.ticks?.roundMinutes?.let { duration ->
Text( val now = LocalClock.current.now
text = it.toString(), val endTimeStr =
) remember(item, now) {
val endTime = now.toLocalTime().plusSeconds(duration.inWholeSeconds)
TimeFormatter.format(endTime)
}
Column {
Text(
text = duration.toString(),
)
Text(
text = stringResource(R.string.ends_at, endTimeStr),
style = MaterialTheme.typography.bodySmall,
)
}
} }
}, },
leadingContent = { leadingContent = {

View file

@ -36,6 +36,7 @@
<string name="download_and_update"><![CDATA[Download & Update]]></string> <string name="download_and_update"><![CDATA[Download & Update]]></string>
<string name="downloading">Downloading…</string> <string name="downloading">Downloading…</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="ends_at">Ends at %1$s</string>
<string name="enter_server_url">Enter Server IP or URL</string> <string name="enter_server_url">Enter Server IP or URL</string>
<string name="enter_server_address">Enter server address</string> <string name="enter_server_address">Enter server address</string>
<string name="episodes">Episodes</string> <string name="episodes">Episodes</string>