mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-05 18:30:59 +01:00
Updates to chapter card UI (#1729)
## Description Changes the chapter cards to use the chapter's image's aspect ratio instead of forcing the video's aspect ratio. These ratios can be different sometimes often for "dummy" chapters. Also, the font size for the chapter name & position is reduced a bit. ### Related issues N/A ### Testing Emulator ## Screenshots <img width="873" height="193" alt="image" src="https://github.com/user-attachments/assets/e1c0e8a8-2e37-4644-bc99-899e20c4af09" /> ## AI or LLM usage None
This commit is contained in:
parent
0a4ab7a547
commit
48005ce67a
8 changed files with 57 additions and 47 deletions
|
|
@ -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<Chapter> =
|
||||
fun fromDto(dto: BaseItemDto): List<Chapter> =
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.github.damontecres.wholphin.data.model.Chapter
|
|||
@Composable
|
||||
fun ChapterRow(
|
||||
chapters: List<Chapter>,
|
||||
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) } },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -570,7 +570,7 @@ class PlaybackViewModel
|
|||
}
|
||||
}
|
||||
|
||||
val chapters = Chapter.fromDto(base, api)
|
||||
val chapters = Chapter.fromDto(base)
|
||||
_state.update {
|
||||
it.copy(currentItemPlayback = itemPlaybackToUse)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ fun ChapterRowOverlay(
|
|||
controllerViewState: ControllerViewState,
|
||||
chapters: List<Chapter>,
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue