Show last played date on overview dialog (#1248)

## Description
Adds a line to the overview/media info dialog with the date when the
item was last played.

### Related issues
N/A

### Testing
Emulator

## Screenshots
<img width="1280" height="720" alt="last_played Large"
src="https://github.com/user-attachments/assets/1c38141d-1000-43d0-bf28-a0845edfacc2"
/>

## AI or LLM usage
None
This commit is contained in:
Ray 2026-04-16 00:26:57 -04:00 committed by GitHub
parent b18416c954
commit b5422fe82b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 35 additions and 60 deletions

View file

@ -47,6 +47,8 @@ fun formatDateTime(dateTime: LocalDateTime): String = getDateFormatter().format(
fun formatDate(dateTime: LocalDate): String = getDateFormatter().format(dateTime)
fun formatDate(dateTime: LocalDateTime): String = getDateFormatter().format(dateTime)
fun toLocalDate(date: String?): LocalDate? =
date?.let {
try {

View file

@ -19,9 +19,12 @@ import androidx.compose.ui.unit.dp
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.data.model.studioNames
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.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.util.StreamFormatting.formatAudioCodec
@ -33,6 +36,7 @@ import org.jellyfin.sdk.model.api.MediaStreamType
import org.jellyfin.sdk.model.api.VideoRange
import org.jellyfin.sdk.model.api.VideoRangeType
import org.jellyfin.sdk.model.extensions.ticks
import java.time.LocalDateTime
import java.util.Locale
data class ItemDetailsDialogInfo(
@ -41,7 +45,17 @@ data class ItemDetailsDialogInfo(
val genres: List<String>,
val files: List<MediaSourceInfo>,
val studios: List<String> = emptyList(),
)
val lastPlayed: LocalDateTime? = null,
) {
constructor(item: BaseItem) : this(
title = item.name ?: "",
overview = item.data.overview,
genres = item.data.genres.orEmpty(),
files = item.data.mediaSources.orEmpty(),
studios = item.studioNames,
lastPlayed = item.data.userData?.lastPlayedDate,
)
}
/**
* Dialog showing metadata about an item
@ -62,6 +76,7 @@ fun ItemDetailsDialog(
val bitrateLabel = stringResource(R.string.bitrate)
val unknown = stringResource(R.string.unknown)
val runtimeLabel = stringResource(R.string.runtime_sort)
val lastPlayedLabel = stringResource(R.string.last_played)
ScrollableDialog(
onDismissRequest = onDismissRequest,
@ -93,6 +108,14 @@ fun ItemDetailsDialog(
style = MaterialTheme.typography.bodyMedium,
)
}
val lastPlayed =
remember(info.lastPlayed) { info.lastPlayed?.let { formatDate(it) } }
if (lastPlayed != null) {
Text(
text = "$lastPlayedLabel: $lastPlayed",
style = MaterialTheme.typography.bodyMedium,
)
}
}
}

View file

@ -191,13 +191,7 @@ fun CollectionDetails(
modifier = modifier,
overviewOnClick = {
val collection = state.collection!!
overviewDialog =
ItemDetailsDialogInfo(
title = collection.title ?: "",
overview = collection.data.overview,
genres = collection.data.genres.orEmpty(),
files = emptyList(),
)
overviewDialog = ItemDetailsDialogInfo(collection)
},
favoriteOnClick =
remember {

View file

@ -149,12 +149,7 @@ fun EpisodeDetails(
},
overviewOnClick = {
overviewDialog =
ItemDetailsDialogInfo(
title = ep.name ?: context.getString(R.string.unknown),
overview = ep.data.overview,
genres = ep.data.genres.orEmpty(),
files = ep.data.mediaSources.orEmpty(),
)
ItemDetailsDialogInfo(ep)
},
moreOnClick = {
moreDialog =
@ -218,13 +213,7 @@ fun EpisodeDetails(
onShowOverview = {
val source = chosenStreams?.source ?: ep.data.mediaSources?.firstOrNull()
if (source != null) {
overviewDialog =
ItemDetailsDialogInfo(
title = ep.name ?: context.getString(R.string.unknown),
overview = ep.data.overview,
genres = ep.data.genres.orEmpty(),
files = listOf(source),
)
overviewDialog = ItemDetailsDialogInfo(ep)
}
},
onClearChosenStreams = {

View file

@ -35,7 +35,6 @@ import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.Person
import com.github.damontecres.wholphin.data.model.Trailer
import com.github.damontecres.wholphin.data.model.aspectRatioFloat
import com.github.damontecres.wholphin.data.model.studioNames
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.ui.AspectRatios
@ -181,13 +180,7 @@ fun MovieDetails(
},
overviewOnClick = {
overviewDialog =
ItemDetailsDialogInfo(
title = movie.name ?: unknownStr,
overview = movie.data.overview,
genres = movie.data.genres.orEmpty(),
files = movie.data.mediaSources.orEmpty(),
studios = movie.studioNames,
)
ItemDetailsDialogInfo(movie)
},
moreOnClick = {
moreDialog =
@ -250,13 +243,7 @@ fun MovieDetails(
}
},
onShowOverview = {
overviewDialog =
ItemDetailsDialogInfo(
title = movie.name ?: unknownStr,
overview = movie.data.overview,
genres = movie.data.genres.orEmpty(),
files = movie.data.mediaSources.orEmpty(),
)
overviewDialog = ItemDetailsDialogInfo(movie)
},
onClearChosenStreams = {
viewModel.clearChosenStreams(chosenStreams)

View file

@ -210,14 +210,7 @@ fun SeriesDetails(
}
},
overviewOnClick = {
overviewDialog =
ItemDetailsDialogInfo(
title = item.name ?: context.getString(R.string.unknown),
overview = item.data.overview,
genres = item.data.genres.orEmpty(),
studios = item.studioNames,
files = listOf(),
)
overviewDialog = ItemDetailsDialogInfo(item)
},
playOnClick = { shuffle ->
if (shuffle) {

View file

@ -265,13 +265,7 @@ fun SeriesOverview(
}
},
onShowOverview = {
overviewDialog =
ItemDetailsDialogInfo(
title = ep.name ?: context.getString(R.string.unknown),
overview = ep.data.overview,
genres = ep.data.genres.orEmpty(),
files = ep.data.mediaSources.orEmpty(),
)
overviewDialog = ItemDetailsDialogInfo(ep)
},
onClearChosenStreams = {
viewModel.clearChosenStreams(ep, chosenStreams)
@ -357,15 +351,7 @@ fun SeriesOverview(
},
overviewOnClick = {
episodeList?.getOrNull(position.episodeRowIndex)?.let {
scope.launchDefault {
overviewDialog =
ItemDetailsDialogInfo(
title = it.name ?: context.getString(R.string.unknown),
overview = it.data.overview,
genres = it.data.genres.orEmpty(),
files = it.data.mediaSources.orEmpty(),
)
}
overviewDialog = ItemDetailsDialogInfo(it)
}
},
personOnClick = {

View file

@ -756,6 +756,7 @@
<string name="discover_tv">Discover TV Shows</string>
<string name="discover_movies">Discover Movies</string>
<string name="studios_in">Studios in %1$s</string>
<string name="last_played">Last played</string>
<string name="ass_subtitle_mode_libass">Direct play with libass</string>
<string name="ass_subtitle_mode_exoplayer">Direct play with ExoPlayer built-in</string>