Refactor media models to use MediaUiModel for improved consistency and type safety

This commit is contained in:
2026-04-17 21:13:45 +02:00
parent 5a377fc428
commit 632dda52d6
27 changed files with 310 additions and 494 deletions

View File

@@ -5,27 +5,37 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.feature.browse.home.FocusableItem import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.core.ui.model.SeriesUiModel
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.feature.browse.home.PosterItem
import java.util.UUID import java.util.UUID
@Composable @Composable
fun PosterCard( fun PosterCard(
item: PosterItem, item: MediaUiModel,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageModifier: Modifier = Modifier, imageModifier: Modifier = Modifier,
posterWidth: Dp = 144.dp, posterWidth: Dp = 144.dp,
showSecondaryText: Boolean = false, showSecondaryText: Boolean = false,
indicatorSize: Int = 28, indicatorSize: Int = 28,
indicatorPadding: Dp = 8.dp, indicatorPadding: Dp = 8.dp,
onFocusedItem: (FocusableItem) -> Unit = {}, onFocusedItem: (MediaUiModel) -> Unit = {},
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
) { ) {
PosterCardContent( PosterCardContent(
model = item.toPosterCardModel(), model = item,
onClick = { item.open(onMovieSelected, onSeriesSelected, onEpisodeSelected) }, onClick = {
// TODO Fix it by addig a handler into the viewModel like onMediaUiModelSelected
when (item) {
is SeriesUiModel -> onSeriesSelected(item.id)
is MovieUiModel -> onMovieSelected(item.id)
is EpisodeUiModel -> onEpisodeSelected(item.seriesId, item.seasonId, item.id)
}
},
modifier = modifier, modifier = modifier,
imageModifier = imageModifier, imageModifier = imageModifier,
posterWidth = posterWidth, posterWidth = posterWidth,

View File

@@ -15,20 +15,19 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import hu.bbara.purefin.core.model.LibraryKind
import hu.bbara.purefin.feature.browse.home.AppViewModel import hu.bbara.purefin.feature.browse.home.AppViewModel
import hu.bbara.purefin.ui.screen.home.TvHomeScreen import hu.bbara.purefin.ui.screen.home.TvHomeScreen
import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestination import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestination
import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestinationItem import hu.bbara.purefin.ui.screen.home.components.TvDrawerDestinationItem
import hu.bbara.purefin.ui.screen.home.components.TvNavigationDrawer import hu.bbara.purefin.ui.screen.home.components.TvNavigationDrawer
import hu.bbara.purefin.ui.screen.libraries.TvLibrariesOverviewScreen import hu.bbara.purefin.ui.screen.libraries.TvLibrariesOverviewScreen
import hu.bbara.purefin.core.model.LibraryKind
@Composable @Composable
fun TvAppScreen( fun TvAppScreen(
viewModel: AppViewModel = hiltViewModel(), viewModel: AppViewModel = hiltViewModel(),
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val serverUrl by viewModel.serverUrl.collectAsState()
val libraries by viewModel.libraries.collectAsState() val libraries by viewModel.libraries.collectAsState()
val continueWatching by viewModel.continueWatching.collectAsState() val continueWatching by viewModel.continueWatching.collectAsState()
val nextUp by viewModel.nextUp.collectAsState() val nextUp by viewModel.nextUp.collectAsState()
@@ -77,7 +76,6 @@ fun TvAppScreen(
libraryContent = latestLibraryContent, libraryContent = latestLibraryContent,
continueWatching = continueWatching, continueWatching = continueWatching,
nextUp = nextUp, nextUp = nextUp,
serverUrl = serverUrl,
onMovieSelected = viewModel::onMovieSelected, onMovieSelected = viewModel::onMovieSelected,
onSeriesSelected = viewModel::onSeriesSelected, onSeriesSelected = viewModel::onSeriesSelected,
onEpisodeSelected = viewModel::onEpisodeSelected, onEpisodeSelected = viewModel::onEpisodeSelected,

View File

@@ -9,10 +9,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import hu.bbara.purefin.ui.screen.home.components.TvFocusedItemHero import hu.bbara.purefin.ui.screen.home.components.TvFocusedItemHero
import hu.bbara.purefin.ui.screen.home.components.TvHomeContent import hu.bbara.purefin.ui.screen.home.components.TvHomeContent
import hu.bbara.purefin.ui.screen.home.components.rememberTvHomeHeroState import hu.bbara.purefin.ui.screen.home.components.rememberTvHomeHeroState
@@ -25,10 +23,9 @@ private val TvHomeMaxHeroHeight = 200.dp
@Composable @Composable
fun TvHomeScreen( fun TvHomeScreen(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
serverUrl: String,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
@@ -66,7 +63,8 @@ fun TvHomeScreen(
libraryContent = libraryContent, libraryContent = libraryContent,
continueWatching = continueWatching, continueWatching = continueWatching,
nextUp = nextUp, nextUp = nextUp,
onMediaFocused = heroState.onMediaFocused, //TODO implement it by throwing out complex TvHomeHeroState shit ass code. Fuck ai btw
onMediaFocused = {},
onMovieSelected = onMovieSelected, onMovieSelected = onMovieSelected,
onSeriesSelected = onSeriesSelected, onSeriesSelected = onSeriesSelected,
onEpisodeSelected = onEpisodeSelected, onEpisodeSelected = onEpisodeSelected,

View File

@@ -1,122 +0,0 @@
package hu.bbara.purefin.ui.screen.home.components
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.core.model.Movie
import hu.bbara.purefin.core.model.Series
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
import hu.bbara.purefin.feature.browse.home.FocusableItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import java.util.UUID
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.image.ArtworkKind
import kotlin.math.roundToInt
internal data class TvFocusedHeroModel(
val id: UUID,
val backdropImageUrl: String,
val title: String,
val metadata: List<String>,
) {
val metadataText: String?
get() = metadata.takeIf { it.isNotEmpty() }?.joinToString("")
}
internal fun FocusableItem.toTvFocusedHeroModel(): TvFocusedHeroModel {
return when (this) {
is ContinueWatchingItem -> when (type) {
MediaKind.MOVIE -> movie!!.toTvFocusedHeroModel()
MediaKind.EPISODE -> episode!!.toTvFocusedHeroModel()
else -> unsupportedType(type)
}
is NextUpItem -> episode.toTvFocusedHeroModel()
is PosterItem -> when (type) {
MediaKind.MOVIE -> movie!!.toTvFocusedHeroModel()
MediaKind.EPISODE -> episode!!.toTvFocusedHeroModel()
MediaKind.SERIES -> series!!.toTvFocusedHeroModel()
else -> unsupportedType(type)
}
}
}
internal fun backdropImageUrl(imageUrlPrefix: String?, fallbackImageUrl: String): String {
val backdropImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.BACKDROP)
return backdropImageUrl.ifBlank { fallbackImageUrl }
}
private fun Movie.toTvFocusedHeroModel(): TvFocusedHeroModel {
return TvFocusedHeroModel(
id = id,
backdropImageUrl = backdropImageUrl(
imageUrlPrefix = imageUrlPrefix,
fallbackImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY)
),
title = title,
metadata = listOf(year, rating, runtime, format).compactMetadata(),
)
}
private fun Episode.toTvFocusedHeroModel(): TvFocusedHeroModel {
return TvFocusedHeroModel(
id = id,
backdropImageUrl = backdropImageUrl(
imageUrlPrefix = imageUrlPrefix,
fallbackImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY)
),
title = title,
metadata = listOf(
"Episode $index",
releaseDate,
runtime,
rating,
format
).compactMetadata(),
)
}
private fun Series.toTvFocusedHeroModel(): TvFocusedHeroModel {
val unwatchedText = if (unwatchedEpisodeCount > 0) {
"$unwatchedEpisodeCount unwatched"
} else {
null
}
return TvFocusedHeroModel(
id = id,
backdropImageUrl = backdropImageUrl(
imageUrlPrefix = imageUrlPrefix,
fallbackImageUrl = ImageUrlBuilder.finishImageUrl(imageUrlPrefix, ArtworkKind.PRIMARY)
),
title = name,
metadata = listOf(year, seasonLabel(seasonCount), unwatchedText).compactMetadata(),
)
}
private fun progressFraction(progress: Double?): Float? {
val fraction = progress
?.div(100.0)
?.toFloat()
?.coerceIn(0f, 1f)
return fraction?.takeIf { it > 0f }
}
private fun progressLabel(progressFraction: Float?): String? {
return progressFraction?.let { "${(it * 100).roundToInt()}%" }
}
private fun List<String?>.compactMetadata(): List<String> {
return map(String?::orEmpty)
.map(String::trim)
.filter(String::isNotBlank)
}
private fun seasonLabel(seasonCount: Int): String {
return if (seasonCount == 1) "1 season" else "$seasonCount seasons"
}
private fun unsupportedType(type: MediaKind): Nothing {
throw UnsupportedOperationException("Unsupported item type: $type")
}

View File

@@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
internal const val TvHomeHeroTitleTag = "tv-home-hero-title" internal const val TvHomeHeroTitleTag = "tv-home-hero-title"
@@ -32,7 +33,7 @@ private const val TvHomeHeroAnimationMillis = 180
@Composable @Composable
internal fun TvFocusedItemHero( internal fun TvFocusedItemHero(
item: TvFocusedHeroModel, item: MediaUiModel,
height: Dp, height: Dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@@ -44,7 +45,7 @@ internal fun TvFocusedItemHero(
.background(scheme.background) .background(scheme.background)
) { ) {
Crossfade( Crossfade(
targetState = item.backdropImageUrl, targetState = item.imageUrl,
animationSpec = tween(durationMillis = TvHomeHeroAnimationMillis), animationSpec = tween(durationMillis = TvHomeHeroAnimationMillis),
label = "tv-home-hero-background" label = "tv-home-hero-background"
) { imageUrl -> ) { imageUrl ->
@@ -98,7 +99,7 @@ internal fun TvFocusedItemHero(
modifier = Modifier.widthIn(max = 720.dp) modifier = Modifier.widthIn(max = 720.dp)
) { ) {
Text( Text(
text = hero.title, text = hero.primaryText,
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 34.sp, fontSize = 34.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -107,9 +108,8 @@ internal fun TvFocusedItemHero(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.testTag(TvHomeHeroTitleTag) modifier = Modifier.testTag(TvHomeHeroTitleTag)
) )
hero.metadataText?.let { metadataText ->
Text( Text(
text = metadataText, text = hero.description,
color = scheme.onSurfaceVariant, color = scheme.onSurfaceVariant,
fontSize = 14.sp, fontSize = 14.sp,
lineHeight = 18.sp, lineHeight = 18.sp,
@@ -121,4 +121,3 @@ internal fun TvFocusedItemHero(
} }
} }
} }
}

View File

@@ -24,11 +24,8 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.FocusableItem
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import java.util.UUID import java.util.UUID
internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item" internal const val TvHomeInitialFocusTag = "tv-home-initial-focus-item"
@@ -37,10 +34,10 @@ internal const val TvHomeContentViewportTag = "tv-home-content-viewport"
@Composable @Composable
fun TvHomeContent( fun TvHomeContent(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
onMediaFocused: (FocusableItem) -> Unit, onMediaFocused: (MediaUiModel) -> Unit,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,

View File

@@ -5,24 +5,24 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.FocusableItem import hu.bbara.purefin.feature.browse.home.FocusableItem
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import java.util.UUID import java.util.UUID
//TODO throw this out and simplify it.
internal data class TvHomeItemRegistry( internal data class TvHomeItemRegistry(
val visibleLibraries: List<LibraryItem>, val visibleLibraries: List<LibraryItem>,
private val libraryContent: Map<UUID, List<PosterItem>>, private val libraryContent: Map<UUID, List<MediaUiModel>>,
private val continueWatching: List<ContinueWatchingItem>, private val continueWatching: List<MediaUiModel>,
private val nextUp: List<NextUpItem>, private val nextUp: List<MediaUiModel>,
val firstAvailableItemId: UUID?, val firstAvailableItemId: UUID?,
) { ) {
val firstAvailableItem: FocusableItem? val firstAvailableItem: MediaUiModel?
get() = firstAvailableItemId?.let(::itemById) get() = firstAvailableItemId?.let(::itemById)
fun itemById(id: UUID): FocusableItem? { fun itemById(id: UUID): MediaUiModel? {
return continueWatching.firstOrNull { it.id == id } return continueWatching.firstOrNull { it.id == id }
?: nextUp.firstOrNull { it.id == id } ?: nextUp.firstOrNull { it.id == id }
?: visibleLibraries.asSequence() ?: visibleLibraries.asSequence()
@@ -33,16 +33,16 @@ internal data class TvHomeItemRegistry(
} }
internal class TvHomeHeroState( internal class TvHomeHeroState(
val focusedHero: TvFocusedHeroModel?, val focusedHero: MediaUiModel?,
val onMediaFocused: (FocusableItem) -> Unit, val onMediaFocused: (FocusableItem) -> Unit,
) )
@Composable @Composable
internal fun rememberTvHomeItemRegistry( internal fun rememberTvHomeItemRegistry(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
): TvHomeItemRegistry { ): TvHomeItemRegistry {
return remember(libraries, libraryContent, continueWatching, nextUp) { return remember(libraries, libraryContent, continueWatching, nextUp) {
createTvHomeItemRegistry( createTvHomeItemRegistry(
@@ -56,9 +56,9 @@ internal fun rememberTvHomeItemRegistry(
internal fun createTvHomeItemRegistry( internal fun createTvHomeItemRegistry(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
): TvHomeItemRegistry { ): TvHomeItemRegistry {
val visibleLibraries = libraries.filter { libraryContent[it.id]?.isEmpty() != true } val visibleLibraries = libraries.filter { libraryContent[it.id]?.isEmpty() != true }
val firstAvailableItemId = continueWatching.firstOrNull()?.id val firstAvailableItemId = continueWatching.firstOrNull()?.id
@@ -79,9 +79,9 @@ internal fun createTvHomeItemRegistry(
@Composable @Composable
internal fun rememberTvHomeHeroState( internal fun rememberTvHomeHeroState(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
): TvHomeHeroState { ): TvHomeHeroState {
val itemRegistry = rememberTvHomeItemRegistry( val itemRegistry = rememberTvHomeItemRegistry(
libraries = libraries, libraries = libraries,
@@ -94,7 +94,7 @@ internal fun rememberTvHomeHeroState(
focusedItemId?.let(itemRegistry::itemById) focusedItemId?.let(itemRegistry::itemById)
} }
val focusedHero = remember(focusedItem, itemRegistry) { val focusedHero = remember(focusedItem, itemRegistry) {
(focusedItem ?: itemRegistry.firstAvailableItem)?.toTvFocusedHeroModel() (focusedItem ?: itemRegistry.firstAvailableItem)
} }
val onMediaFocused: (FocusableItem) -> Unit = remember { val onMediaFocused: (FocusableItem) -> Unit = remember {
{ item -> { item ->

View File

@@ -44,17 +44,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import hu.bbara.purefin.ui.common.card.PosterCard import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.bar.MediaProgressBar
import hu.bbara.purefin.ui.common.card.PosterCard
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
import hu.bbara.purefin.feature.browse.home.FocusableItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import java.util.UUID import java.util.UUID
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.image.ArtworkKind
private val TvHomeSectionsThumbShape = RoundedCornerShape(20.dp) private val TvHomeSectionsThumbShape = RoundedCornerShape(20.dp)
private val TvHomeSectionsHorizontalPadding = 32.dp private val TvHomeSectionsHorizontalPadding = 32.dp
@@ -70,8 +65,8 @@ internal fun tvHomeLibraryRowTag(libraryId: UUID): String =
@Composable @Composable
fun TvContinueWatchingSection( fun TvContinueWatchingSection(
items: List<ContinueWatchingItem>, items: List<MediaUiModel>,
onFocusedItem: (FocusableItem) -> Unit = {}, onFocusedItem: (MediaUiModel) -> Unit = {},
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
firstItemFocusRequester: FocusRequester? = null, firstItemFocusRequester: FocusRequester? = null,
@@ -88,24 +83,11 @@ fun TvContinueWatchingSection(
rowTestTag = rowTestTag rowTestTag = rowTestTag
) { ) {
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item -> itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
val progressFraction = (item.progress / 100.0).toFloat().coerceIn(0f, 1f)
TvHomeLandscapeCard( TvHomeLandscapeCard(
title = item.primaryText, title = item.primaryText,
supporting = item.secondaryText, supporting = item.secondaryText,
imageUrl = when (item.type) { imageUrl = item.imageUrl,
MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl( progress = item.progress ?: 0f,
prefixImageUrl = item.movie?.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = item.episode?.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
else -> null
},
progress = progressFraction,
imageModifier = Modifier imageModifier = Modifier
.then( .then(
if (index == 0 && firstItemFocusRequester != null) { if (index == 0 && firstItemFocusRequester != null) {
@@ -123,13 +105,9 @@ fun TvContinueWatchingSection(
), ),
onFocusedItem = { onFocusedItem(item) }, onFocusedItem = { onFocusedItem(item) },
onClick = { onClick = {
when (item.type) { when (item) {
MediaKind.MOVIE -> onMovieSelected(item.movie!!.id) //TODO fix this shit
MediaKind.EPISODE -> { is EpisodeUiModel -> onEpisodeSelected(item.seriesId, item.seasonId, item.id)
val episode = item.episode!!
onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id)
}
else -> Unit else -> Unit
} }
} }
@@ -140,8 +118,8 @@ fun TvContinueWatchingSection(
@Composable @Composable
fun TvNextUpSection( fun TvNextUpSection(
items: List<NextUpItem>, items: List<MediaUiModel>,
onFocusedItem: (FocusableItem) -> Unit = {}, onFocusedItem: (MediaUiModel) -> Unit = {},
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
firstItemFocusRequester: FocusRequester? = null, firstItemFocusRequester: FocusRequester? = null,
firstItemTestTag: String? = null, firstItemTestTag: String? = null,
@@ -178,9 +156,11 @@ fun TvNextUpSection(
), ),
onFocusedItem = { onFocusedItem(item) }, onFocusedItem = { onFocusedItem(item) },
onClick = { onClick = {
val episode = item.episode //TODO FIX
(item as? EpisodeUiModel)?.let { episode ->
onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id) onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id)
} }
}
) )
} }
} }
@@ -189,8 +169,8 @@ fun TvNextUpSection(
@Composable @Composable
fun TvLibraryPosterSection( fun TvLibraryPosterSection(
title: String, title: String,
items: List<PosterItem>, items: List<MediaUiModel>,
onFocusedItem: (FocusableItem) -> Unit = {}, onFocusedItem: (MediaUiModel) -> Unit = {},
firstItemFocusRequester: FocusRequester? = null, firstItemFocusRequester: FocusRequester? = null,
firstItemTestTag: String? = null, firstItemTestTag: String? = null,
rowTestTag: String? = null, rowTestTag: String? = null,

View File

@@ -22,11 +22,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.ui.common.card.PosterCard
import hu.bbara.purefin.ui.common.button.PurefinIconButton
import hu.bbara.purefin.core.navigation.LibraryDto import hu.bbara.purefin.core.navigation.LibraryDto
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.library.LibraryViewModel import hu.bbara.purefin.feature.browse.library.LibraryViewModel
import hu.bbara.purefin.ui.common.button.PurefinIconButton
import hu.bbara.purefin.ui.common.card.PosterCard
import java.util.UUID import java.util.UUID
@Composable @Composable
@@ -92,7 +92,7 @@ private fun TvLibraryTopBar(
@Composable @Composable
fun TvLibraryContent( fun TvLibraryContent(
libraryItems: List<PosterItem>, libraryItems: List<MediaUiModel>,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier

View File

@@ -3,20 +3,30 @@ package hu.bbara.purefin.ui.common.card
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.core.ui.model.SeriesUiModel
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.feature.browse.home.PosterItem
import java.util.UUID import java.util.UUID
@Composable @Composable
fun PosterCard( fun PosterCard(
item: PosterItem, item: MediaUiModel,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
) { ) {
PosterCardContent( PosterCardContent(
model = item.toPosterCardModel(), model = item,
onClick = { item.open(onMovieSelected, onSeriesSelected, onEpisodeSelected) }, onClick = {
//TODO same throw this shit out when to composite onSelect is finished
when (item) {
is MovieUiModel -> onMovieSelected(item.id)
is SeriesUiModel -> onSeriesSelected(item.id)
else -> Unit
}
},
modifier = modifier modifier = modifier
) )
} }

View File

@@ -9,29 +9,26 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.SuggestedItem
import hu.bbara.purefin.ui.screen.AppBottomBar
import hu.bbara.purefin.ui.screen.home.components.HomeContent import hu.bbara.purefin.ui.screen.home.components.HomeContent
import hu.bbara.purefin.ui.screen.home.components.HomeTopBar import hu.bbara.purefin.ui.screen.home.components.HomeTopBar
import hu.bbara.purefin.ui.screen.home.components.search.HomeSearchOverlay import hu.bbara.purefin.ui.screen.home.components.search.HomeSearchOverlay
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import hu.bbara.purefin.feature.browse.home.SuggestedItem
import hu.bbara.purefin.ui.screen.AppBottomBar
import java.util.UUID import java.util.UUID
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeScreen( fun HomeScreen(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
suggestions: List<SuggestedItem>, suggestions: List<SuggestedItem>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
isRefreshing: Boolean, isRefreshing: Boolean,
onRefresh: () -> Unit, onRefresh: () -> Unit,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
@@ -46,14 +43,6 @@ fun HomeScreen(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var isSearchVisible by rememberSaveable { mutableStateOf(false) } var isSearchVisible by rememberSaveable { mutableStateOf(false) }
val subtitle = remember(continueWatching, nextUp, libraries) {
when {
continueWatching.isNotEmpty() -> "Continue where you left off"
nextUp.isNotEmpty() -> "Fresh episodes waiting for you"
libraries.isNotEmpty() -> "Browse your latest additions"
else -> "Pull to refresh or explore your libraries"
}
}
Scaffold( Scaffold(
modifier = modifier modifier = modifier

View File

@@ -22,26 +22,24 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.NextUpItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import hu.bbara.purefin.feature.browse.home.SuggestedItem import hu.bbara.purefin.feature.browse.home.SuggestedItem
import hu.bbara.purefin.ui.screen.home.components.continuewatching.ContinueWatchingSection import hu.bbara.purefin.ui.screen.home.components.continuewatching.ContinueWatchingSection
import hu.bbara.purefin.ui.screen.home.components.featured.SuggestionsSection import hu.bbara.purefin.ui.screen.home.components.featured.SuggestionsSection
import hu.bbara.purefin.ui.screen.home.components.library.LibraryPosterSection import hu.bbara.purefin.ui.screen.home.components.library.LibraryPosterSection
import hu.bbara.purefin.ui.screen.home.components.nextup.NextUpSection import hu.bbara.purefin.ui.screen.home.components.nextup.NextUpSection
import java.util.UUID import java.util.UUID
import hu.bbara.purefin.core.model.MediaKind
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun HomeContent( fun HomeContent(
libraries: List<LibraryItem>, libraries: List<LibraryItem>,
libraryContent: Map<UUID, List<PosterItem>>, libraryContent: Map<UUID, List<MediaUiModel>>,
suggestions: List<SuggestedItem>, suggestions: List<SuggestedItem>,
continueWatching: List<ContinueWatchingItem>, continueWatching: List<MediaUiModel>,
nextUp: List<NextUpItem>, nextUp: List<MediaUiModel>,
isRefreshing: Boolean, isRefreshing: Boolean,
onRefresh: () -> Unit, onRefresh: () -> Unit,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,

View File

@@ -23,46 +23,21 @@ import androidx.compose.ui.layout.ContentScale
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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.ui.common.bar.MediaProgressBar import hu.bbara.purefin.ui.common.bar.MediaProgressBar
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
import java.util.UUID import java.util.UUID
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.image.ArtworkKind
@Composable @Composable
internal fun ContinueWatchingCard( internal fun ContinueWatchingCard(
item: ContinueWatchingItem, item: MediaUiModel,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val supportingText = when (item.type) {
MediaKind.MOVIE -> listOf(
item.movie?.year,
item.movie?.runtime
).filterNotNull().filter { it.isNotBlank() }.joinToString("")
MediaKind.EPISODE -> listOf(
"Episode ${item.episode?.index}",
item.episode?.runtime
).filterNotNull().filter { it.isNotBlank() }.joinToString("")
else -> ""
}
val imageUrl = when (item.type) {
MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = item.movie?.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = item.episode?.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
else -> null
}
Surface( Surface(
shape = RoundedCornerShape(26.dp), shape = RoundedCornerShape(26.dp),
@@ -73,13 +48,10 @@ internal fun ContinueWatchingCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
when (item.type) { // TODO fix this shit as well
MediaKind.MOVIE -> onMovieSelected(item.movie!!.id) when (item) {
MediaKind.EPISODE -> { is MovieUiModel -> onMovieSelected(item.id)
val episode = item.episode!! is EpisodeUiModel -> onEpisodeSelected(item.seriesId, item.seasonId, item.id)
onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id)
}
else -> Unit else -> Unit
} }
} }
@@ -90,14 +62,12 @@ internal fun ContinueWatchingCard(
.aspectRatio(16f / 9f) .aspectRatio(16f / 9f)
.background(scheme.surfaceContainer) .background(scheme.surfaceContainer)
) { ) {
if (imageUrl != null) {
PurefinAsyncImage( PurefinAsyncImage(
model = imageUrl, model = item.imageUrl,
contentDescription = item.primaryText, contentDescription = item.primaryText,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
}
Box( Box(
modifier = Modifier modifier = Modifier
.matchParentSize() .matchParentSize()
@@ -111,13 +81,15 @@ internal fun ContinueWatchingCard(
) )
) )
) )
item.progress?.let { progress ->
MediaProgressBar( MediaProgressBar(
progress = (item.progress.toFloat() / 100f).coerceIn(0f, 1f), progress = progress,
foregroundColor = scheme.primary, foregroundColor = scheme.primary,
backgroundColor = Color.White.copy(alpha = 0.24f), backgroundColor = Color.White.copy(alpha = 0.24f),
modifier = Modifier.align(Alignment.BottomStart) modifier = Modifier.align(Alignment.BottomStart)
) )
} }
}
Column( Column(
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp) modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp)
@@ -129,9 +101,8 @@ internal fun ContinueWatchingCard(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
if (supportingText.isNotBlank()) {
Text( Text(
text = supportingText, text = item.secondaryText,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = scheme.onSurface, color = scheme.onSurface,
maxLines = 1, maxLines = 1,
@@ -141,4 +112,3 @@ internal fun ContinueWatchingCard(
} }
} }
} }
}

View File

@@ -9,13 +9,13 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
import hu.bbara.purefin.feature.browse.home.ContinueWatchingItem
import java.util.UUID import java.util.UUID
@Composable @Composable
fun ContinueWatchingSection( fun ContinueWatchingSection(
items: List<ContinueWatchingItem>, items: List<MediaUiModel>,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier

View File

@@ -23,39 +23,23 @@ import androidx.compose.ui.layout.ContentScale
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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.badge.UnwatchedEpisodeBadge import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.core.ui.model.SeriesUiModel
import hu.bbara.purefin.ui.common.badge.WatchStateBadge import hu.bbara.purefin.ui.common.badge.WatchStateBadge
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import java.util.UUID import java.util.UUID
@Composable @Composable
internal fun HomeBrowseCard( internal fun HomeBrowseCard(
item: PosterItem, uiModel: MediaUiModel,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val scheme = MaterialTheme.colorScheme val scheme = MaterialTheme.colorScheme
val supportingText = when (item.type) {
MediaKind.MOVIE -> listOf(
item.movie?.year,
item.movie?.runtime
).filterNotNull().filter { it.isNotBlank() }.joinToString("")
MediaKind.SERIES -> item.series!!.let { series ->
if (series.seasonCount == 1) "1 season" else "${series.seasonCount} seasons"
}
MediaKind.EPISODE -> listOf(
"Episode ${item.episode?.index}",
item.episode?.runtime
).filterNotNull().filter { it.isNotBlank() }.joinToString("")
else -> ""
}
Surface( Surface(
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
@@ -66,15 +50,11 @@ internal fun HomeBrowseCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
when (item.type) { //TODO fix this shit
MediaKind.MOVIE -> onMovieSelected(item.id) when (uiModel) {
MediaKind.SERIES -> onSeriesSelected(item.id) is MovieUiModel -> onMovieSelected(uiModel.id)
MediaKind.EPISODE -> { is SeriesUiModel -> onSeriesSelected(uiModel.id)
val episode = item.episode!! is EpisodeUiModel -> onEpisodeSelected(uiModel.seriesId, uiModel.seasonId, uiModel.id)
onEpisodeSelected(episode.seriesId, episode.seasonId, episode.id)
}
else -> Unit
} }
} }
) { ) {
@@ -86,62 +66,37 @@ internal fun HomeBrowseCard(
.background(scheme.surface) .background(scheme.surface)
) { ) {
PurefinAsyncImage( PurefinAsyncImage(
model = item.imageUrl, model = uiModel.imageUrl,
contentDescription = item.title, contentDescription = uiModel.primaryText,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
when (item.type) { when (uiModel) {
MediaKind.MOVIE -> { is MovieUiModel, is EpisodeUiModel -> {
val movie = item.movie!!
WatchStateBadge( WatchStateBadge(
size = 28, size = 28,
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(8.dp), .padding(8.dp),
watched = movie.watched, watched = uiModel.watched,
started = (movie.progress ?: 0.0) > 0 started = (uiModel.progress ?: 0f) > 0f
) )
} }
MediaKind.EPISODE -> {
val episode = item.episode!!
WatchStateBadge(
size = 28,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.dp),
watched = episode.watched,
started = (episode.progress ?: 0.0) > 0
)
}
MediaKind.SERIES -> {
UnwatchedEpisodeBadge(
size = 28,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.dp),
unwatchedCount = item.series!!.unwatchedEpisodeCount
)
}
else -> Unit else -> Unit
} }
} }
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
Column(modifier = modifier.padding(12.dp)) { Column(modifier = modifier.padding(12.dp)) {
Text( Text(
text = item.title, text = uiModel.primaryText,
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
if (supportingText.isNotBlank()) {
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = supportingText, text = uiModel.secondaryText,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = scheme.onSurface, color = scheme.onSurface,
maxLines = 1, maxLines = 1,
@@ -151,4 +106,3 @@ internal fun HomeBrowseCard(
} }
} }
} }
}

View File

@@ -9,15 +9,15 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.home.LibraryItem import hu.bbara.purefin.feature.browse.home.LibraryItem
import hu.bbara.purefin.feature.browse.home.PosterItem
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
import java.util.UUID import java.util.UUID
@Composable @Composable
fun LibraryPosterSection( fun LibraryPosterSection(
library: LibraryItem, library: LibraryItem,
items: List<PosterItem>, items: List<MediaUiModel>,
onLibrarySelected: (LibraryItem) -> Unit, onLibrarySelected: (LibraryItem) -> Unit,
onMovieSelected: (UUID) -> Unit, onMovieSelected: (UUID) -> Unit,
onSeriesSelected: (UUID) -> Unit, onSeriesSelected: (UUID) -> Unit,
@@ -42,7 +42,7 @@ fun LibraryPosterSection(
) { ) {
items(items = items, key = { item -> item.id }) { item -> items(items = items, key = { item -> item.id }) { item ->
HomeBrowseCard( HomeBrowseCard(
item = item, uiModel = item,
onMovieSelected = onMovieSelected, onMovieSelected = onMovieSelected,
onSeriesSelected = onSeriesSelected, onSeriesSelected = onSeriesSelected,
onEpisodeSelected = onEpisodeSelected onEpisodeSelected = onEpisodeSelected

View File

@@ -22,15 +22,14 @@ import androidx.compose.ui.layout.ContentScale
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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.feature.browse.home.NextUpItem
import java.util.UUID import java.util.UUID
import hu.bbara.purefin.core.image.ArtworkKind
@Composable @Composable
internal fun NextUpCard( internal fun NextUpCard(
item: NextUpItem, uiModel: MediaUiModel,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -45,9 +44,11 @@ internal fun NextUpCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { .clickable {
onEpisodeSelected( // TODO fix this shit
item.episode.seriesId, item.episode.seasonId, item.episode.id when (uiModel) {
) is EpisodeUiModel -> onEpisodeSelected(uiModel.seriesId, uiModel.seasonId, uiModel.id)
else -> Unit
}
} }
) { ) {
Box( Box(
@@ -57,8 +58,8 @@ internal fun NextUpCard(
.background(scheme.surface) .background(scheme.surface)
) { ) {
PurefinAsyncImage( PurefinAsyncImage(
model = ImageUrlBuilder.finishImageUrl(item.episode.imageUrlPrefix, ArtworkKind.PRIMARY), model = uiModel.imageUrl,
contentDescription = item.primaryText, contentDescription = uiModel.primaryText,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
@@ -81,16 +82,14 @@ internal fun NextUpCard(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp) modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp)
) { ) {
Text( Text(
text = item.primaryText, text = uiModel.primaryText,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
Text( Text(
text = listOf("Episode ${item.episode.index}", item.episode.runtime, item.secondaryText) text = uiModel.secondaryText,
.filter { it.isNotBlank() }
.joinToString(""),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = scheme.onSurface, color = scheme.onSurface,
maxLines = 1, maxLines = 1,

View File

@@ -9,13 +9,13 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.ui.common.header.SectionHeader import hu.bbara.purefin.ui.common.header.SectionHeader
import hu.bbara.purefin.feature.browse.home.NextUpItem
import java.util.UUID import java.util.UUID
@Composable @Composable
fun NextUpSection( fun NextUpSection(
items: List<NextUpItem>, items: List<MediaUiModel>,
onEpisodeSelected: (UUID, UUID, UUID) -> Unit, onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@@ -33,7 +33,7 @@ fun NextUpSection(
) { ) {
items(items = items, key = { item -> item.id }) { item -> items(items = items, key = { item -> item.id }) { item ->
NextUpCard( NextUpCard(
item = item, uiModel = item,
onEpisodeSelected = onEpisodeSelected onEpisodeSelected = onEpisodeSelected
) )
} }

View File

@@ -21,11 +21,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import hu.bbara.purefin.ui.common.card.PosterCard
import hu.bbara.purefin.ui.common.button.PurefinIconButton
import hu.bbara.purefin.core.navigation.LibraryDto import hu.bbara.purefin.core.navigation.LibraryDto
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.feature.browse.library.LibraryViewModel import hu.bbara.purefin.feature.browse.library.LibraryViewModel
import hu.bbara.purefin.ui.common.button.PurefinIconButton
import hu.bbara.purefin.ui.common.card.PosterCard
@Composable @Composable
fun LibraryScreen( fun LibraryScreen(
@@ -72,7 +72,7 @@ internal fun LibraryTopBar(
@Composable @Composable
internal fun LibraryPosterGrid( internal fun LibraryPosterGrid(
libraryItems: List<PosterItem>, libraryItems: List<MediaUiModel>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: LibraryViewModel = hiltViewModel() viewModel: LibraryViewModel = hiltViewModel()
) { ) {

View File

@@ -10,7 +10,3 @@ kotlin {
} }
jvmToolchain(11) jvmToolchain(11)
} }
dependencies {
implementation(project(":core:model"))
}

View File

@@ -10,3 +10,7 @@ kotlin {
} }
jvmToolchain(11) jvmToolchain(11)
} }
dependencies {
implementation(project(":core:image"))
}

View File

@@ -0,0 +1,85 @@
package hu.bbara.purefin.core.ui.model
import hu.bbara.purefin.core.image.ArtworkKind
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.core.model.Movie
import hu.bbara.purefin.core.model.Series
import java.util.UUID
sealed interface MediaUiModel {
val id: UUID
val primaryText: String
val secondaryText: String
val description: String
val imageUrl: String
val progress: Float?
get() = null
val watched: Boolean
get() = false
}
class MovieUiModel: MediaUiModel {
override val id: UUID
override val primaryText: String
override val secondaryText: String
override val description: String
override val imageUrl: String
override val progress: Float?
constructor(movie: Movie) {
id = movie.id
primaryText = movie.title
secondaryText = movie.year
description = movie.synopsis
imageUrl = ImageUrlBuilder.finishImageUrl(
prefixImageUrl = movie.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
progress = (movie.progress?.toFloat() ?: 0f) / 100f
}
}
class SeriesUiModel : MediaUiModel {
override val id: UUID
override val primaryText: String
override val secondaryText: String
override val description: String
override val imageUrl: String
constructor(series: Series) {
id = series.id
primaryText = series.name
secondaryText = "${series.seasonCount} seasons"
description = series.synopsis
imageUrl = ImageUrlBuilder.finishImageUrl(
prefixImageUrl = series.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
}
}
class EpisodeUiModel : MediaUiModel {
override val id: UUID
override val primaryText: String
override val secondaryText: String
override val description: String
override val imageUrl: String
override val progress: Float?
val seriesId: UUID
val seasonId: UUID
constructor(episode: Episode) {
id = episode.id
primaryText = episode.title
secondaryText = episode.releaseDate
description = episode.synopsis
imageUrl = ImageUrlBuilder.finishImageUrl(
prefixImageUrl = episode.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
progress = (episode.progress?.toFloat() ?: 0f) / 100f
seriesId = episode.seriesId
seasonId = episode.seasonId
}
}

View File

@@ -30,7 +30,9 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.ui.common.badge.UnwatchedEpisodeBadge import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.ui.common.badge.WatchStateBadge import hu.bbara.purefin.ui.common.badge.WatchStateBadge
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
@@ -57,7 +59,7 @@ sealed interface PosterCardBadge {
@Composable @Composable
fun PosterCardContent( fun PosterCardContent(
model: PosterCardModel, model: MediaUiModel,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageModifier: Modifier = Modifier, imageModifier: Modifier = Modifier,
@@ -108,36 +110,25 @@ fun PosterCardContent(
.clickable(onClick = onClick), .clickable(onClick = onClick),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
when (val badge = model.badge) { when (model) {
is PosterCardBadge.WatchState -> { is MovieUiModel, is EpisodeUiModel -> {
WatchStateBadge( WatchStateBadge(
size = indicatorSize, size = indicatorSize,
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.padding(indicatorPadding), .padding(indicatorPadding),
watched = badge.watched, watched = model.watched,
started = badge.started started = (model.progress ?: 0f) > 0f
) )
} }
else -> Unit
is PosterCardBadge.UnwatchedEpisodes -> {
UnwatchedEpisodeBadge(
size = indicatorSize,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(indicatorPadding),
unwatchedCount = badge.count
)
}
PosterCardBadge.None -> Unit
} }
} }
Column( Column(
modifier = Modifier.padding(top = 8.dp, start = 4.dp, end = 4.dp, bottom = 8.dp) modifier = Modifier.padding(top = 8.dp, start = 4.dp, end = 4.dp, bottom = 8.dp)
) { ) {
Text( Text(
text = model.title, text = model.primaryText,
color = scheme.onBackground, color = scheme.onBackground,
fontSize = 13.sp, fontSize = 13.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,

View File

@@ -5,18 +5,19 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.data.HomeRepository import hu.bbara.purefin.core.data.HomeRepository
import hu.bbara.purefin.core.data.MediaCatalogReader import hu.bbara.purefin.core.data.MediaCatalogReader
import hu.bbara.purefin.core.download.MediaDownloadController
import hu.bbara.purefin.core.data.session.UserSessionRepository import hu.bbara.purefin.core.data.session.UserSessionRepository
import hu.bbara.purefin.core.download.MediaDownloadController
import hu.bbara.purefin.core.ui.model.EpisodeUiModel
import hu.bbara.purefin.core.model.LibraryKind import hu.bbara.purefin.core.model.LibraryKind
import hu.bbara.purefin.core.model.Media import hu.bbara.purefin.core.model.Media
import hu.bbara.purefin.core.model.MediaKind import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.core.ui.model.SeriesUiModel
import hu.bbara.purefin.core.navigation.EpisodeDto import hu.bbara.purefin.core.navigation.EpisodeDto
import hu.bbara.purefin.core.navigation.LibraryDto import hu.bbara.purefin.core.navigation.LibraryDto
import hu.bbara.purefin.core.navigation.MovieDto import hu.bbara.purefin.core.navigation.MovieDto
import hu.bbara.purefin.core.navigation.NavigationManager import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.core.navigation.SeriesDto
import java.util.UUID
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@@ -95,10 +97,10 @@ class AppViewModel @Inject constructor(
list.mapNotNull { media -> list.mapNotNull { media ->
when (media) { when (media) {
is Media.MovieMedia -> moviesMap[media.movieId]?.let { is Media.MovieMedia -> moviesMap[media.movieId]?.let {
ContinueWatchingItem(type = MediaKind.MOVIE, movie = it) MovieUiModel(movie = it)
} }
is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let { is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let {
ContinueWatchingItem(type = MediaKind.EPISODE, episode = it) EpisodeUiModel(episode = it)
} }
else -> null else -> null
} }
@@ -116,7 +118,7 @@ class AppViewModel @Inject constructor(
list.mapNotNull { media -> list.mapNotNull { media ->
when (media) { when (media) {
is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let { is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let {
NextUpItem(episode = it) EpisodeUiModel(episode = it)
} }
else -> null else -> null
} }
@@ -137,16 +139,16 @@ class AppViewModel @Inject constructor(
items.mapNotNull { media -> items.mapNotNull { media ->
when (media) { when (media) {
is Media.MovieMedia -> moviesMap[media.movieId]?.let { is Media.MovieMedia -> moviesMap[media.movieId]?.let {
PosterItem(type = MediaKind.MOVIE, movie = it) MovieUiModel(movie = it)
} }
is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let { is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let {
PosterItem(type = MediaKind.EPISODE, episode = it) EpisodeUiModel(episode = it)
} }
is Media.SeriesMedia -> seriesMap[media.seriesId]?.let { is Media.SeriesMedia -> seriesMap[media.seriesId]?.let {
PosterItem(type = MediaKind.SERIES, series = it) SeriesUiModel(series = it)
} }
is Media.SeasonMedia -> seriesMap[media.seriesId]?.let { is Media.SeasonMedia -> seriesMap[media.seriesId]?.let {
PosterItem(type = MediaKind.SERIES, series = it) SeriesUiModel(series = it)
} }
} }
}.distinctBy { it.id } }.distinctBy { it.id }

View File

@@ -1,13 +1,13 @@
package hu.bbara.purefin.feature.browse.home package hu.bbara.purefin.feature.browse.home
import hu.bbara.purefin.core.image.ArtworkKind
import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.core.model.LibraryKind
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Movie
import hu.bbara.purefin.core.model.Series import hu.bbara.purefin.core.model.Series
import java.util.UUID import java.util.UUID
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.model.LibraryKind
import hu.bbara.purefin.core.image.ArtworkKind
sealed interface SuggestedItem { sealed interface SuggestedItem {
@@ -97,49 +97,6 @@ sealed interface FocusableItem {
val type: MediaKind val type: MediaKind
} }
data class ContinueWatchingItem(
override val type: MediaKind,
val movie: Movie? = null,
val episode: Episode? = null,
) : FocusableItem {
override val id: UUID = when (type) {
MediaKind.MOVIE -> movie!!.id
MediaKind.EPISODE -> episode!!.id
else -> throw UnsupportedOperationException("Unsupported item type: $type")
}
override val primaryText: String = when (type) {
MediaKind.MOVIE -> movie!!.title
MediaKind.EPISODE -> episode!!.title
else -> throw UnsupportedOperationException("Unsupported item type: $type")
}
override val secondaryText: String = when (type) {
MediaKind.MOVIE -> movie!!.year
MediaKind.EPISODE -> episode!!.releaseDate
else -> throw UnsupportedOperationException("Unsupported item type: $type")
}
override val imageUrl: String = when (type) {
MediaKind.MOVIE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = movie!!.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
MediaKind.EPISODE -> ImageUrlBuilder.finishImageUrl(
prefixImageUrl = episode!!.imageUrlPrefix,
artworkKind = ArtworkKind.PRIMARY
)
else -> throw IllegalArgumentException("Invalid type: $type")
}
override val description: String = when (type) {
MediaKind.MOVIE -> movie!!.synopsis
MediaKind.EPISODE -> episode!!.synopsis
else -> throw UnsupportedOperationException("Unsupported item type: $type")
}
val progress: Double = when (type) {
MediaKind.MOVIE -> movie!!.progress ?: 0.0
MediaKind.EPISODE -> episode!!.progress ?: 0.0
else -> throw UnsupportedOperationException("Unsupported item type: $type")
}
}
data class NextUpItem( data class NextUpItem(
val episode: Episode val episode: Episode
) : FocusableItem { ) : FocusableItem {

View File

@@ -5,19 +5,20 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.data.HomeRepository import hu.bbara.purefin.core.data.HomeRepository
import hu.bbara.purefin.core.model.LibraryKind import hu.bbara.purefin.core.model.LibraryKind
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.feature.browse.home.PosterItem
import hu.bbara.purefin.core.navigation.MovieDto import hu.bbara.purefin.core.navigation.MovieDto
import hu.bbara.purefin.core.navigation.NavigationManager import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.core.navigation.SeriesDto
import java.util.UUID import hu.bbara.purefin.core.ui.model.MediaUiModel
import hu.bbara.purefin.core.ui.model.MovieUiModel
import hu.bbara.purefin.core.ui.model.SeriesUiModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@@ -28,7 +29,7 @@ class LibraryViewModel @Inject constructor(
private val selectedLibrary = MutableStateFlow<UUID?>(null) private val selectedLibrary = MutableStateFlow<UUID?>(null)
val contents: StateFlow<List<PosterItem>> = combine(selectedLibrary, homeRepository.libraries) { val contents: StateFlow<List<MediaUiModel>> = combine(selectedLibrary, homeRepository.libraries) {
libraryId, libraries -> libraryId, libraries ->
if (libraryId == null) { if (libraryId == null) {
return@combine emptyList() return@combine emptyList()
@@ -36,10 +37,10 @@ class LibraryViewModel @Inject constructor(
val library = libraries.find { it.id == libraryId } ?: return@combine emptyList() val library = libraries.find { it.id == libraryId } ?: return@combine emptyList()
when (library.type) { when (library.type) {
LibraryKind.SERIES -> library.series!!.map { series -> LibraryKind.SERIES -> library.series!!.map { series ->
PosterItem(type = MediaKind.SERIES, series = series) SeriesUiModel(series)
} }
LibraryKind.MOVIES -> library.movies!!.map { movie -> LibraryKind.MOVIES -> library.movies!!.map { movie ->
PosterItem(type = MediaKind.MOVIE, movie = movie) MovieUiModel(movie)
} }
} }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

View File

@@ -5,19 +5,19 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import hu.bbara.purefin.core.data.OfflineCatalogReader import hu.bbara.purefin.core.data.OfflineCatalogReader
import hu.bbara.purefin.core.download.MediaDownloadController import hu.bbara.purefin.core.download.MediaDownloadController
import hu.bbara.purefin.core.image.ArtworkKind
import hu.bbara.purefin.core.image.ImageUrlBuilder import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.core.model.MediaKind
import hu.bbara.purefin.core.navigation.MovieDto import hu.bbara.purefin.core.navigation.MovieDto
import hu.bbara.purefin.core.navigation.NavigationManager import hu.bbara.purefin.core.navigation.NavigationManager
import hu.bbara.purefin.core.navigation.Route import hu.bbara.purefin.core.navigation.Route
import hu.bbara.purefin.core.navigation.SeriesDto import hu.bbara.purefin.core.navigation.SeriesDto
import hu.bbara.purefin.feature.browse.home.PosterItem import hu.bbara.purefin.core.ui.model.MovieUiModel
import java.util.UUID import hu.bbara.purefin.core.ui.model.SeriesUiModel
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import hu.bbara.purefin.core.image.ArtworkKind import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@@ -53,8 +53,8 @@ class DownloadsViewModel @Inject constructor(
) { movies, series, inProgress -> ) { movies, series, inProgress ->
movies.values movies.values
.filter { it.id.toString() !in inProgress } .filter { it.id.toString() !in inProgress }
.map { PosterItem(type = MediaKind.MOVIE, movie = it) } + .map { MovieUiModel(it) } +
series.values.map { PosterItem(type = MediaKind.SERIES, series = it) } series.values.map { SeriesUiModel(it) }
} }
/** Items currently being downloaded with their progress. */ /** Items currently being downloaded with their progress. */