diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/Chapter.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/Chapter.kt index 3e07964c..71a300fb 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/Chapter.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/Chapter.kt @@ -1,38 +1,30 @@ package com.github.damontecres.wholphin.data.model -import org.jellyfin.sdk.api.client.ApiClient -import org.jellyfin.sdk.api.client.extensions.imageApi import org.jellyfin.sdk.model.api.BaseItemDto -import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.extensions.ticks +import java.util.UUID import kotlin.time.Duration /** * Represents a chapter within a video */ data class Chapter( + val itemId: UUID, val name: String?, val position: Duration, - val imageUrl: String?, + val tag: String?, + val index: Int, ) { companion object { - fun fromDto( - dto: BaseItemDto, - api: ApiClient, - ): List = + fun fromDto(dto: BaseItemDto): List = dto.chapters ?.mapIndexed { index, chapter -> Chapter( - chapter.name, - chapter.startPositionTicks.ticks, - chapter.imageTag?.let { - api.imageApi.getItemImageUrl( - itemId = dto.id, - imageType = ImageType.CHAPTER, - tag = it, - imageIndex = index, - ) - }, + itemId = dto.id, + name = chapter.name, + position = chapter.startPositionTicks.ticks, + tag = chapter.imageTag, + index = index, ) }?.sortedBy { it.position } .orEmpty() 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 d3c87124..634cc988 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 @@ -5,42 +5,62 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times import androidx.tv.material3.Card +import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import coil3.compose.AsyncImage +import com.github.damontecres.wholphin.data.model.Chapter 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.formatDuration import com.github.damontecres.wholphin.ui.roundSeconds -import kotlin.time.Duration +import org.jellyfin.sdk.model.api.ImageType /** * Card for a [com.github.damontecres.wholphin.data.model.Chapter] */ @Composable fun ChapterCard( - name: String?, - position: Duration, - imageUrl: String?, + chapter: Chapter, onClick: () -> Unit, - aspectRatio: Float, modifier: Modifier = Modifier, cardHeight: Dp = 120.dp, onLongClick: (() -> Unit)? = null, interactionSource: MutableInteractionSource? = null, ) { + val density = LocalDensity.current + val imageUrlService = LocalImageUrlService.current + val imageUrl = + remember(chapter, cardHeight) { + imageUrlService.getItemImageUrl( + itemId = chapter.itemId, + imageType = ImageType.CHAPTER, + tag = chapter.tag, + imageIndex = chapter.index, + fillHeight = with(density) { cardHeight.roundToPx() }, + ) + } + var width by remember { mutableStateOf(AspectRatios.WIDE * cardHeight) } Card( - modifier = modifier.size(cardHeight * aspectRatio, cardHeight), + modifier = modifier.height(cardHeight), onClick = onClick, onLongClick = onLongClick, interactionSource = interactionSource, @@ -51,24 +71,36 @@ fun ChapterCard( contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier.fillMaxSize(), + onSuccess = { + width = + with(density) { + it.painter.intrinsicSize.width + .toDp() + } + }, ) Box( modifier = Modifier + .width(width) .align(Alignment.BottomStart) - .fillMaxWidth() .background(AppColors.TransparentBlack50), ) { Column(modifier = Modifier.padding(4.dp)) { - name?.let { + chapter.name?.let { Text( text = it, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold, ) } val resources = LocalResources.current - val positionText = remember(position) { resources.formatDuration(position.roundSeconds) } + val positionText = + remember(chapter.position) { resources.formatDuration(chapter.position.roundSeconds) } Text( text = positionText, + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Normal, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterRow.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterRow.kt index b1e64a2a..5fa6316f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterRow.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/ChapterRow.kt @@ -21,7 +21,6 @@ import com.github.damontecres.wholphin.data.model.Chapter @Composable fun ChapterRow( chapters: List, - aspectRatio: Float, onClick: (Chapter) -> Unit, modifier: Modifier = Modifier, onLongClick: ((Chapter) -> Unit)? = null, @@ -47,11 +46,8 @@ fun ChapterRow( ) { itemsIndexed(chapters) { index, item -> ChapterCard( - name = item.name, - position = item.position, - imageUrl = item.imageUrl, + chapter = item, onClick = { onClick(item) }, - aspectRatio = aspectRatio, modifier = Modifier, onLongClick = onLongClick?.let { { it(item) } }, ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt index bc4a3461..db4608ac 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieDetails.kt @@ -33,10 +33,8 @@ import com.github.damontecres.wholphin.data.model.BaseItem 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.preferences.UserPreferences import com.github.damontecres.wholphin.services.TrailerService -import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus import com.github.damontecres.wholphin.ui.cards.ChapterRow @@ -412,7 +410,6 @@ fun MovieDetailsContent( item { ChapterRow( chapters = chapters, - aspectRatio = movie.data.aspectRatioFloat ?: AspectRatios.WIDE, onClick = { position = CHAPTER_ROW playOnClick.invoke(it.position) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt index 9df28425..49a7d742 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/movie/MovieViewModel.kt @@ -117,7 +117,7 @@ class MovieViewModel userPreferencesService.getCurrent(), ) val remoteTrailers = trailerService.getRemoteTrailers(movie) - val chapters = Chapter.fromDto(movie.data, api) + val chapters = Chapter.fromDto(movie.data) _state.update { it.copy( loading = DataLoadingState.Success(movie), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index fae25907..2dc01d36 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -570,7 +570,7 @@ class PlaybackViewModel } } - val chapters = Chapter.fromDto(base, api) + val chapters = Chapter.fromDto(base) _state.update { it.copy(currentItemPlayback = itemPlaybackToUse) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/ChapterRowOverlay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/ChapterRowOverlay.kt index 45e335ab..fa32950b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/ChapterRowOverlay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/overlay/ChapterRowOverlay.kt @@ -44,7 +44,6 @@ fun ChapterRowOverlay( controllerViewState: ControllerViewState, chapters: List, hasNext: Boolean, - aspectRatio: Float, onChangeState: (OverlayViewState) -> Unit, modifier: Modifier = Modifier, ) { @@ -115,10 +114,7 @@ fun ChapterRowOverlay( if (isFocused) controllerViewState.pulseControls() } ChapterCard( - name = chapter.name, - position = chapter.position, - imageUrl = chapter.imageUrl, - aspectRatio = aspectRatio, + chapter = chapter, onClick = { player.seekTo(chapter.position.inWholeMilliseconds) controllerViewState.hideControls() 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 a6ade035..9af4a171 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 @@ -52,9 +52,7 @@ import coil3.compose.AsyncImage import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.Chapter import com.github.damontecres.wholphin.data.model.PlaylistItem -import com.github.damontecres.wholphin.data.model.aspectRatioFloat 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 @@ -228,7 +226,6 @@ fun PlaybackOverlay( controllerViewState = controllerViewState, chapters = chapters, hasNext = nextEnabled, - aspectRatio = item?.data?.aspectRatioFloat ?: AspectRatios.WIDE, onChangeState = onChangeState, modifier = Modifier