Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ internal fun AllUpNextShowView(
onShowClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val isPremiere = item.progress.nextEpisode?.isPremiere() == true
val isFinale = item.progress.nextEpisode?.isFinale() == true
val isPremiere = item.progress.nextEpisode?.isPremiere(item.progress.isLatestAired) == true
val isFinale = item.progress.nextEpisode?.isFinale(item.progress.isLatestAired) == true

PanelMediaCard(
enabled = enabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ private fun Preview() {
),
nextEpisode = PreviewData.episode1,
lastEpisode = null,
isLatestAired = false,
),
show = PreviewData.show1,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal data class Progress(
val stats: Stats?,
val nextEpisode: Episode?,
val lastEpisode: Episode?,
val isLatestAired: Boolean,
) {
@Immutable
internal data class Stats(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,31 @@ internal fun HomeUpNextShowView(
Column(
verticalArrangement = Arrangement.spacedBy(3.dp),
) {
val isLatestAired = item.progress.isLatestAired
when {
item.progress.nextEpisode?.isPremiere() == true -> PremiereChip(
contentTextStyle = TraktTheme.typography.meta.copy(
fontSize = 10.sp,
),
modifier = Modifier
.shadow(2.dp, RoundedCornerShape(100))
.height(20.dp),
)
item.progress.nextEpisode?.isFinale() == true -> FinaleChip(
contentTextStyle = TraktTheme.typography.meta.copy(
fontSize = 10.sp,
),
modifier = Modifier
.shadow(
2.dp,
androidx.compose.foundation.shape.RoundedCornerShape(100),
)
.height(20.dp),
)
item.progress.nextEpisode?.isPremiere(isLatestAired) == true -> {
PremiereChip(
contentTextStyle = TraktTheme.typography.meta.copy(
fontSize = 10.sp,
),
modifier = Modifier
.shadow(2.dp, RoundedCornerShape(100))
.height(20.dp),
)
}
item.progress.nextEpisode?.isFinale(isLatestAired) == true -> {
FinaleChip(
contentTextStyle = TraktTheme.typography.meta.copy(
fontSize = 10.sp,
),
modifier = Modifier
.shadow(
2.dp,
androidx.compose.foundation.shape.RoundedCornerShape(100),
)
.height(20.dp),
)
}
}

EpisodeProgressBar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ internal class GetUpNextUseCase(

return remoteItems
.asyncMap { item ->
val nextEpisode = item.progress.nextEpisode?.let { Episode.fromDto(it) }
val lastEpisode = item.progress.lastEpisode?.let { Episode.fromDto(it) }
UpNextShow(
show = Show.fromDto(item.show),
progress = Progress(
Expand All @@ -96,12 +98,14 @@ internal class GetUpNextUseCase(
minutesLeft = it.minutesLeft,
)
},
lastEpisode = item.progress.lastEpisode?.let {
Episode.fromDto(it)
},
nextEpisode = item.progress.nextEpisode?.let {
Episode.fromDto(it)
},
lastEpisode = lastEpisode,
nextEpisode = nextEpisode,
// FIXME: progress.last_episode is the user's furthest watched episode, not the
// show's latest aired episode, so we can't compare directly. As a proxy, treat
// the next episode as the latest aired when remaining (aired - completed) is 1
// or less - i.e. no further aired episode exists beyond the displayed one.
// Replace once the API surfaces an absolute "latest aired episode" reference.
isLatestAired = (item.progress.aired - item.progress.completed) <= 1,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tv.trakt.trakt.core.summary.ui

import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
Expand All @@ -21,6 +22,7 @@ import tv.trakt.trakt.common.helpers.extensions.rememberDurationFormat
import tv.trakt.trakt.common.helpers.extensions.toLocal
import tv.trakt.trakt.common.helpers.preview.PreviewData
import tv.trakt.trakt.common.model.Episode
import tv.trakt.trakt.common.model.EpisodeType
import tv.trakt.trakt.common.model.MediaGenre
import tv.trakt.trakt.common.model.MediaStatus
import tv.trakt.trakt.common.model.Movie
Expand Down Expand Up @@ -77,6 +79,7 @@ internal fun DetailsMetaInfo(
episode.releasedAt?.toLocal()?.toLocalDate()
},
runtime = episode.runtime,
episodeType = episode.type,
directors = episodeDirectors,
writers = episodeWriters,
episodeRowsOnly = true,
Expand Down Expand Up @@ -120,6 +123,7 @@ private fun DetailsMetaInfo(
network: String? = null,
titleOriginal: String? = null,
episodesCount: Int? = null,
episodeType: EpisodeType? = null,
languages: ImmutableList<String> = EmptyImmutableList,
genres: ImmutableList<MediaGenre> = EmptyImmutableList,
studios: ImmutableList<String>? = null,
Expand Down Expand Up @@ -201,6 +205,19 @@ private fun DetailsMetaInfo(
}
}

if (episodeType != null) {
Row(
horizontalArrangement = spacedBy(16.dp),
) {
DetailsMeta(
title = stringResource(R.string.header_episode_type),
values = listOf(stringResource(episodeType.stringRes)),
modifier = Modifier.weight(1F),
)
Box(modifier = Modifier.weight(1F))
}
}

Row(
horizontalArrangement = spacedBy(16.dp),
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ object PreviewData {
rating = Rating(rating = 4.34f, votes = 5394),
commentCount = 4424,
runtime = 24.minutes,
episodeType = null,
type = null,
originalTitle = "Episode Original Title",
images = Images(
screenshot = listOf(
Expand Down
30 changes: 19 additions & 11 deletions common/src/main/java/tv/trakt/trakt/common/model/Episode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.compose.ui.res.stringResource
import kotlinx.collections.immutable.toImmutableList
import tv.trakt.trakt.common.helpers.extensions.nowUtcInstant
import tv.trakt.trakt.common.helpers.extensions.toInstant
import tv.trakt.trakt.common.model.EpisodeType.MID_SEASON_FINALE
import tv.trakt.trakt.common.model.EpisodeType.MID_SEASON_PREMIERE
import tv.trakt.trakt.common.networking.EpisodeDto
import tv.trakt.trakt.common.networking.EpisodeLikesDto
import tv.trakt.trakt.common.networking.LastEpisodeDto
Expand All @@ -18,6 +20,7 @@ import kotlin.time.Duration.Companion.minutes
@Immutable
data class Episode(
val ids: Ids,
val type: EpisodeType?,
val number: Int,
val season: Int,
val title: String,
Expand All @@ -26,7 +29,6 @@ data class Episode(
val rating: Rating,
val commentCount: Int,
val runtime: Duration?,
val episodeType: String?,
val originalTitle: String,
val images: Images?,
val updatedAt: Instant?,
Expand Down Expand Up @@ -56,29 +58,36 @@ data class Episode(

@Composable
fun seasonEpisodeString(): String {
val string = stringResource(R.string.episode_footer_season_episode, this.season, this.number)
val string = stringResource(
R.string.episode_footer_season_episode,
this.season,
this.number,
)
return when {
title.isNotBlank() -> "$string - $title"
else -> string
}
}

@Composable
fun isPremiere(): Boolean =
remember(episodeType) {
episodeType?.contains("premiere") == true
fun isPremiere(isLatestAired: Boolean = false): Boolean =
remember(type, isLatestAired) {
if (type?.isPremiere == true) return@remember true
type == MID_SEASON_PREMIERE && isLatestAired
}

@Composable
fun isFinale(): Boolean =
remember(episodeType) {
episodeType?.contains("finale") == true
fun isFinale(isLatestAired: Boolean = false): Boolean =
remember(type, isLatestAired) {
if (type?.isFinale == true) return@remember true
type == MID_SEASON_FINALE && isLatestAired
}
}

fun Episode.Companion.fromDto(dto: EpisodeDto): Episode {
return Episode(
ids = Ids.fromDto(dto.ids),
type = dto.episodeType?.let { EpisodeType.fromValue(it.value) },
number = dto.number,
season = dto.season,
title = dto.title ?: "N/A",
Expand All @@ -90,7 +99,6 @@ fun Episode.Companion.fromDto(dto: EpisodeDto): Episode {
),
commentCount = dto.commentCount ?: 0,
runtime = dto.runtime?.minutes,
episodeType = dto.episodeType?.value,
originalTitle = dto.originalTitle ?: "",
images = Images(
screenshot = (dto.images?.screenshot ?: emptyList()).toImmutableList(),
Expand All @@ -104,6 +112,7 @@ fun Episode.Companion.fromDto(dto: EpisodeDto): Episode {
fun Episode.Companion.fromDto(dto: LastEpisodeDto): Episode {
return Episode(
ids = Ids.fromDto(dto.ids),
type = dto.episodeType?.let { EpisodeType.fromValue(it.value) },
number = dto.number,
season = dto.season,
title = dto.title ?: "N/A",
Expand All @@ -115,7 +124,6 @@ fun Episode.Companion.fromDto(dto: LastEpisodeDto): Episode {
),
commentCount = dto.commentCount ?: 0,
runtime = dto.runtime?.minutes,
episodeType = dto.episodeType?.value,
originalTitle = dto.originalTitle ?: "",
images = Images(
screenshot = (dto.images?.screenshot ?: emptyList()).toImmutableList(),
Expand All @@ -129,6 +137,7 @@ fun Episode.Companion.fromDto(dto: LastEpisodeDto): Episode {
fun Episode.Companion.fromDto(dto: EpisodeLikesDto): Episode {
return Episode(
ids = Ids.fromDto(dto.ids),
type = dto.episodeType?.let { EpisodeType.fromValue(it.value) },
number = dto.number,
season = dto.season,
title = dto.title ?: "N/A",
Expand All @@ -140,7 +149,6 @@ fun Episode.Companion.fromDto(dto: EpisodeLikesDto): Episode {
),
commentCount = dto.commentCount ?: 0,
runtime = dto.runtime?.minutes,
episodeType = dto.episodeType?.value,
originalTitle = dto.originalTitle ?: "",
images = Images(
screenshot = (dto.images?.screenshot ?: emptyList()).toImmutableList(),
Expand Down
27 changes: 27 additions & 0 deletions common/src/main/java/tv/trakt/trakt/common/model/EpisodeType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package tv.trakt.trakt.common.model

import androidx.annotation.StringRes
import tv.trakt.trakt.resources.R

enum class EpisodeType(
val value: String,
@param:StringRes val stringRes: Int,
) {
SERIES_PREMIERE("series_premiere", R.string.tag_text_series_premiere),
SERIES_FINALE("series_finale", R.string.tag_text_series_finale),
SEASON_PREMIERE("season_premiere", R.string.tag_text_season_premiere),
SEASON_FINALE("season_finale", R.string.tag_text_season_finale),
MID_SEASON_PREMIERE("mid_season_premiere", R.string.tag_text_mid_season_premiere),
MID_SEASON_FINALE("mid_season_finale", R.string.tag_text_mid_season_finale),
;

val isPremiere: Boolean
get() = this == SERIES_PREMIERE || this == SEASON_PREMIERE

val isFinale: Boolean
get() = this == SERIES_FINALE || this == SEASON_FINALE

companion object {
fun fromValue(value: String): EpisodeType? = entries.find { it.value == value }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ private fun ContentShowListItem(
verticalArrangement = spacedBy(3.dp),
) {
when {
item.progress.nextEpisode?.isPremiere() == true -> PremiereChip()
item.progress.nextEpisode?.isFinale() == true -> FinaleChip()
item.progress.nextEpisode?.isPremiere(item.progress.isLatestAired) == true -> PremiereChip()
item.progress.nextEpisode?.isFinale(item.progress.isLatestAired) == true -> FinaleChip()
}

Row(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal data class ProgressShow(
val stats: Stats?,
val nextEpisode: Episode?,
val lastEpisode: Episode?,
val isLatestAired: Boolean,
) {
@Immutable
internal data class Stats(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ internal class GetShowsUpNextUseCase(
)
return remoteItems
.asyncMap { item ->
val nextEpisode = item.progress.nextEpisode?.let { Episode.fromDto(it) }
val lastEpisode = item.progress.lastEpisode?.let { Episode.fromDto(it) }
ProgressShow(
show = Show.fromDto(item.show),
progress = Progress(
Expand All @@ -45,12 +47,14 @@ internal class GetShowsUpNextUseCase(
minutesLeft = it.minutesLeft,
)
},
lastEpisode = item.progress.lastEpisode?.let {
Episode.fromDto(it)
},
nextEpisode = item.progress.nextEpisode?.let {
Episode.fromDto(it)
},
lastEpisode = lastEpisode,
nextEpisode = nextEpisode,
// FIXME: progress.last_episode is the user's furthest watched episode, not the
// show's latest aired episode, so we can't compare directly. As a proxy, treat
// the next episode as the latest aired when remaining (aired - completed) is 1
// or less - i.e. no further aired episode exists beyond the displayed one.
// Replace once the API surfaces an absolute "latest aired episode" reference.
isLatestAired = (item.progress.aired - item.progress.completed) <= 1,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,14 @@ private fun UpNextViewAllContent(
verticalArrangement = spacedBy(3.dp),
) {
when {
item.progress.nextEpisode?.isPremiere() == true -> {
item.progress.nextEpisode?.isPremiere(item.progress.isLatestAired) == true -> {
PremiereChip(
contentTextStyle = TraktTheme.typography.meta.copy(
fontSize = 10.sp,
),
)
}
item.progress.nextEpisode?.isFinale() == true -> {
item.progress.nextEpisode?.isFinale(item.progress.isLatestAired) == true -> {
FinaleChip(
contentTextStyle = TraktTheme.typography.meta.copy(
fontSize = 10.sp,
Expand Down