mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor: update image handling in Movie dto to use imageUrlPrefix and JellyfinImageHelper
This commit is contained in:
@@ -65,7 +65,7 @@ class MovieScreenContentTest {
|
|||||||
runtime = "164m",
|
runtime = "164m",
|
||||||
format = "4K",
|
format = "4K",
|
||||||
synopsis = "Officer K uncovers a secret that sends him searching for Rick Deckard.",
|
synopsis = "Officer K uncovers a secret that sends him searching for Rick Deckard.",
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee",
|
imageUrlPrefix = "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee",
|
||||||
audioTrack = "ENG 5.1",
|
audioTrack = "ENG 5.1",
|
||||||
subtitles = "ENG",
|
subtitles = "ENG",
|
||||||
cast = listOf(
|
cast = listOf(
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ import hu.bbara.purefin.common.ui.components.MediaDetailOverviewSection
|
|||||||
import hu.bbara.purefin.common.ui.components.MediaDetailPlaybackSection
|
import hu.bbara.purefin.common.ui.components.MediaDetailPlaybackSection
|
||||||
import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle
|
import hu.bbara.purefin.common.ui.components.MediaDetailSectionTitle
|
||||||
import hu.bbara.purefin.common.ui.components.TvMediaDetailScaffold
|
import hu.bbara.purefin.common.ui.components.TvMediaDetailScaffold
|
||||||
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.core.data.navigation.MovieDto
|
import hu.bbara.purefin.core.data.navigation.MovieDto
|
||||||
import hu.bbara.purefin.core.model.Movie
|
import hu.bbara.purefin.core.model.Movie
|
||||||
import hu.bbara.purefin.feature.shared.content.movie.MovieScreenViewModel
|
import hu.bbara.purefin.feature.shared.content.movie.MovieScreenViewModel
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MovieScreen(
|
fun MovieScreen(
|
||||||
@@ -60,7 +62,7 @@ internal fun MovieScreenContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TvMediaDetailScaffold(
|
TvMediaDetailScaffold(
|
||||||
heroImageUrl = movie.heroImageUrl,
|
heroImageUrl = JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.PRIMARY),
|
||||||
resetScrollKey = movie.id,
|
resetScrollKey = movie.id,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|||||||
@@ -39,11 +39,13 @@ import androidx.compose.ui.unit.sp
|
|||||||
import hu.bbara.purefin.common.ui.PosterCard
|
import hu.bbara.purefin.common.ui.PosterCard
|
||||||
import hu.bbara.purefin.common.ui.components.MediaProgressBar
|
import hu.bbara.purefin.common.ui.components.MediaProgressBar
|
||||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||||
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem
|
import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem
|
||||||
import hu.bbara.purefin.feature.shared.home.NextUpItem
|
import hu.bbara.purefin.feature.shared.home.NextUpItem
|
||||||
import hu.bbara.purefin.feature.shared.home.PosterItem
|
import hu.bbara.purefin.feature.shared.home.PosterItem
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
import kotlin.math.nextUp
|
import kotlin.math.nextUp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -67,8 +69,6 @@ fun TvContinueWatchingSection(
|
|||||||
itemsIndexed(items = items) { index, item ->
|
itemsIndexed(items = items) { index, item ->
|
||||||
TvContinueWatchingCard(
|
TvContinueWatchingCard(
|
||||||
item = item,
|
item = item,
|
||||||
isFirstItem = index == 0,
|
|
||||||
isLastItem = index == items.lastIndex,
|
|
||||||
onMovieSelected = onMovieSelected,
|
onMovieSelected = onMovieSelected,
|
||||||
onEpisodeSelected = onEpisodeSelected
|
onEpisodeSelected = onEpisodeSelected
|
||||||
)
|
)
|
||||||
@@ -80,8 +80,6 @@ fun TvContinueWatchingSection(
|
|||||||
fun TvContinueWatchingCard(
|
fun TvContinueWatchingCard(
|
||||||
item: ContinueWatchingItem,
|
item: ContinueWatchingItem,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isFirstItem: Boolean = false,
|
|
||||||
isLastItem: Boolean = false,
|
|
||||||
onMovieSelected: (UUID) -> Unit,
|
onMovieSelected: (UUID) -> Unit,
|
||||||
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
onEpisodeSelected: (UUID, UUID, UUID) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -91,7 +89,10 @@ fun TvContinueWatchingCard(
|
|||||||
val scale by animateFloatAsState(targetValue = if (isFocused) 1.07f else 1.0f, label = "scale")
|
val scale by animateFloatAsState(targetValue = if (isFocused) 1.07f else 1.0f, label = "scale")
|
||||||
|
|
||||||
val imageUrl = when (item.type) {
|
val imageUrl = when (item.type) {
|
||||||
BaseItemKind.MOVIE -> item.movie?.heroImageUrl
|
BaseItemKind.MOVIE -> JellyfinImageHelper.finishImageUrl(
|
||||||
|
prefixImageUrl = item.movie?.imageUrlPrefix,
|
||||||
|
imageType = ImageType.PRIMARY
|
||||||
|
)
|
||||||
BaseItemKind.EPISODE -> item.episode?.heroImageUrl
|
BaseItemKind.EPISODE -> item.episode?.heroImageUrl
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,14 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||||
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.core.data.navigation.MovieDto
|
import hu.bbara.purefin.core.data.navigation.MovieDto
|
||||||
import hu.bbara.purefin.core.model.CastMember
|
import hu.bbara.purefin.core.model.CastMember
|
||||||
import hu.bbara.purefin.core.model.Movie
|
import hu.bbara.purefin.core.model.Movie
|
||||||
import hu.bbara.purefin.feature.download.DownloadState
|
import hu.bbara.purefin.feature.download.DownloadState
|
||||||
import hu.bbara.purefin.feature.shared.content.movie.MovieScreenViewModel
|
import hu.bbara.purefin.feature.shared.content.movie.MovieScreenViewModel
|
||||||
import hu.bbara.purefin.ui.theme.AppTheme
|
import hu.bbara.purefin.ui.theme.AppTheme
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -135,7 +137,7 @@ fun MediaHeroSection(
|
|||||||
.height(sectionHeight)
|
.height(sectionHeight)
|
||||||
) {
|
) {
|
||||||
MediaHero(
|
MediaHero(
|
||||||
imageUrl = movie.heroImageUrl,
|
imageUrl = JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.PRIMARY),
|
||||||
backgroundColor = MaterialTheme.colorScheme.background,
|
backgroundColor = MaterialTheme.colorScheme.background,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
)
|
)
|
||||||
@@ -209,7 +211,7 @@ private fun previewMovie(): Movie =
|
|||||||
runtime = "2h 44m",
|
runtime = "2h 44m",
|
||||||
format = "Dolby Vision",
|
format = "Dolby Vision",
|
||||||
synopsis = "A new blade runner uncovers a buried secret that forces him to trace the vanished footsteps of Rick Deckard.",
|
synopsis = "A new blade runner uncovers a buried secret that forces him to trace the vanished footsteps of Rick Deckard.",
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1519608487953-e999c86e7455",
|
imageUrlPrefix = "https://images.unsplash.com/photo-1519608487953-e999c86e7455",
|
||||||
audioTrack = "English 5.1",
|
audioTrack = "English 5.1",
|
||||||
subtitles = "English CC",
|
subtitles = "English CC",
|
||||||
cast = listOf(
|
cast = listOf(
|
||||||
|
|||||||
@@ -21,26 +21,19 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
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.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import hu.bbara.purefin.app.home.ui.continuewatching.ContinueWatchingSection
|
import hu.bbara.purefin.app.home.ui.continuewatching.ContinueWatchingSection
|
||||||
import hu.bbara.purefin.app.home.ui.featured.SuggestionsSection
|
import hu.bbara.purefin.app.home.ui.featured.SuggestionsSection
|
||||||
import hu.bbara.purefin.app.home.ui.library.LibraryPosterSection
|
import hu.bbara.purefin.app.home.ui.library.LibraryPosterSection
|
||||||
import hu.bbara.purefin.app.home.ui.nextup.NextUpSection
|
import hu.bbara.purefin.app.home.ui.nextup.NextUpSection
|
||||||
import hu.bbara.purefin.app.home.ui.shared.HomeEmptyState
|
import hu.bbara.purefin.app.home.ui.shared.HomeEmptyState
|
||||||
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.shared.home.ContinueWatchingItem
|
import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem
|
||||||
import hu.bbara.purefin.feature.shared.home.LibraryItem
|
import hu.bbara.purefin.feature.shared.home.LibraryItem
|
||||||
import hu.bbara.purefin.feature.shared.home.NextUpItem
|
import hu.bbara.purefin.feature.shared.home.NextUpItem
|
||||||
import hu.bbara.purefin.feature.shared.home.PosterItem
|
import hu.bbara.purefin.feature.shared.home.PosterItem
|
||||||
import hu.bbara.purefin.feature.shared.home.SuggestedItem
|
import hu.bbara.purefin.feature.shared.home.SuggestedItem
|
||||||
import hu.bbara.purefin.ui.theme.AppTheme
|
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
|
||||||
import java.util.UUID as JavaUuid
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -169,266 +162,4 @@ fun HomeContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(name = "Home Full", showBackground = true, widthDp = 412, heightDp = 915)
|
|
||||||
@Composable
|
|
||||||
private fun HomeContentPreview() {
|
|
||||||
AppTheme(darkTheme = true) {
|
|
||||||
HomeContent(
|
|
||||||
libraries = homePreviewLibraries(),
|
|
||||||
libraryContent = homePreviewLibraryContent(),
|
|
||||||
suggestions = emptyList(),
|
|
||||||
continueWatching = homePreviewContinueWatching(),
|
|
||||||
nextUp = homePreviewNextUp(),
|
|
||||||
isRefreshing = false,
|
|
||||||
onRefresh = {},
|
|
||||||
onMovieSelected = {},
|
|
||||||
onSeriesSelected = {},
|
|
||||||
onEpisodeSelected = { _, _, _ -> },
|
|
||||||
onLibrarySelected = {},
|
|
||||||
onBrowseLibrariesClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "Home Libraries Only", showBackground = true, widthDp = 412, heightDp = 915)
|
|
||||||
@Composable
|
|
||||||
private fun HomeLibrariesOnlyPreview() {
|
|
||||||
AppTheme(darkTheme = true) {
|
|
||||||
HomeContent(
|
|
||||||
libraries = homePreviewLibraries(),
|
|
||||||
libraryContent = homePreviewLibraryContent(),
|
|
||||||
suggestions = emptyList(),
|
|
||||||
continueWatching = emptyList(),
|
|
||||||
nextUp = emptyList(),
|
|
||||||
isRefreshing = false,
|
|
||||||
onRefresh = {},
|
|
||||||
onMovieSelected = {},
|
|
||||||
onSeriesSelected = {},
|
|
||||||
onEpisodeSelected = { _, _, _ -> },
|
|
||||||
onLibrarySelected = {},
|
|
||||||
onBrowseLibrariesClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "Home Empty", showBackground = true, widthDp = 412, heightDp = 915)
|
|
||||||
@Composable
|
|
||||||
private fun HomeEmptyPreview() {
|
|
||||||
AppTheme(darkTheme = false) {
|
|
||||||
HomeContent(
|
|
||||||
libraries = emptyList(),
|
|
||||||
libraryContent = emptyMap(),
|
|
||||||
suggestions = emptyList(),
|
|
||||||
continueWatching = emptyList(),
|
|
||||||
nextUp = emptyList(),
|
|
||||||
isRefreshing = false,
|
|
||||||
onRefresh = {},
|
|
||||||
onMovieSelected = {},
|
|
||||||
onSeriesSelected = {},
|
|
||||||
onEpisodeSelected = { _, _, _ -> },
|
|
||||||
onLibrarySelected = {},
|
|
||||||
onBrowseLibrariesClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewLibraries(): List<LibraryItem> {
|
|
||||||
return listOf(
|
|
||||||
LibraryItem(
|
|
||||||
id = JavaUuid.fromString("11111111-1111-1111-1111-111111111111"),
|
|
||||||
name = "Movies",
|
|
||||||
type = CollectionType.MOVIES,
|
|
||||||
posterUrl = "https://images.unsplash.com/photo-1517604931442-7e0c8ed2963c",
|
|
||||||
isEmpty = false
|
|
||||||
),
|
|
||||||
LibraryItem(
|
|
||||||
id = JavaUuid.fromString("22222222-2222-2222-2222-222222222222"),
|
|
||||||
name = "Series",
|
|
||||||
type = CollectionType.TVSHOWS,
|
|
||||||
posterUrl = "https://images.unsplash.com/photo-1489599849927-2ee91cede3ba",
|
|
||||||
isEmpty = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewLibraryContent(): Map<UUID, List<PosterItem>> {
|
|
||||||
val movie = homePreviewMovie(
|
|
||||||
id = "33333333-3333-3333-3333-333333333333",
|
|
||||||
title = "Blade Runner 2049",
|
|
||||||
year = "2017",
|
|
||||||
runtime = "2h 44m",
|
|
||||||
rating = "16+",
|
|
||||||
format = "Dolby Vision",
|
|
||||||
synopsis = "A young blade runner uncovers a buried secret that pulls him toward a vanished legend.",
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1519608487953-e999c86e7455",
|
|
||||||
progress = 42.0,
|
|
||||||
watched = false
|
|
||||||
)
|
|
||||||
val secondMovie = homePreviewMovie(
|
|
||||||
id = "44444444-4444-4444-4444-444444444444",
|
|
||||||
title = "Arrival",
|
|
||||||
year = "2016",
|
|
||||||
runtime = "1h 56m",
|
|
||||||
rating = "12+",
|
|
||||||
format = "4K",
|
|
||||||
synopsis = "A linguist is recruited when mysterious spacecraft touch down around the world.",
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429",
|
|
||||||
progress = null,
|
|
||||||
watched = false
|
|
||||||
)
|
|
||||||
val series = homePreviewSeries()
|
|
||||||
val episode = homePreviewEpisode(
|
|
||||||
id = "66666666-6666-6666-6666-666666666666",
|
|
||||||
title = "Signals",
|
|
||||||
index = 2,
|
|
||||||
releaseDate = "2025",
|
|
||||||
runtime = "48m",
|
|
||||||
rating = "16+",
|
|
||||||
progress = 18.0,
|
|
||||||
watched = false,
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1520034475321-cbe63696469a",
|
|
||||||
synopsis = "Anomalies around the station point to a cover-up."
|
|
||||||
)
|
|
||||||
|
|
||||||
return mapOf(
|
|
||||||
homePreviewLibraries()[0].id to listOf(
|
|
||||||
PosterItem(type = BaseItemKind.MOVIE, movie = movie),
|
|
||||||
PosterItem(type = BaseItemKind.MOVIE, movie = secondMovie)
|
|
||||||
),
|
|
||||||
homePreviewLibraries()[1].id to listOf(
|
|
||||||
PosterItem(type = BaseItemKind.SERIES, series = series),
|
|
||||||
PosterItem(type = BaseItemKind.EPISODE, episode = episode)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewContinueWatching(): List<ContinueWatchingItem> {
|
|
||||||
return listOf(
|
|
||||||
ContinueWatchingItem(
|
|
||||||
type = BaseItemKind.MOVIE,
|
|
||||||
movie = homePreviewMovie(
|
|
||||||
id = "77777777-7777-7777-7777-777777777777",
|
|
||||||
title = "Dune: Part Two",
|
|
||||||
year = "2024",
|
|
||||||
runtime = "2h 46m",
|
|
||||||
rating = "13+",
|
|
||||||
format = "IMAX",
|
|
||||||
synopsis = "Paul Atreides unites with the Fremen while seeking justice and revenge.",
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1446776811953-b23d57bd21aa",
|
|
||||||
progress = 58.0,
|
|
||||||
watched = false
|
|
||||||
)
|
|
||||||
),
|
|
||||||
ContinueWatchingItem(
|
|
||||||
type = BaseItemKind.EPISODE,
|
|
||||||
episode = homePreviewEpisode(
|
|
||||||
id = "88888888-8888-8888-8888-888888888888",
|
|
||||||
title = "A Fresh Start",
|
|
||||||
index = 1,
|
|
||||||
releaseDate = "2025",
|
|
||||||
runtime = "51m",
|
|
||||||
rating = "16+",
|
|
||||||
progress = 23.0,
|
|
||||||
watched = false,
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1497032205916-ac775f0649ae",
|
|
||||||
synopsis = "A fractured crew tries to reassemble after a year apart."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewNextUp(): List<NextUpItem> {
|
|
||||||
return listOf(
|
|
||||||
NextUpItem(
|
|
||||||
episode = homePreviewEpisode(
|
|
||||||
id = "99999999-9999-9999-9999-999999999999",
|
|
||||||
title = "Return Window",
|
|
||||||
index = 3,
|
|
||||||
releaseDate = "2025",
|
|
||||||
runtime = "54m",
|
|
||||||
rating = "16+",
|
|
||||||
progress = null,
|
|
||||||
watched = false,
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee",
|
|
||||||
synopsis = "A high-risk jump changes the rules of the mission."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewMovie(
|
|
||||||
id: String,
|
|
||||||
title: String,
|
|
||||||
year: String,
|
|
||||||
runtime: String,
|
|
||||||
rating: String,
|
|
||||||
format: String,
|
|
||||||
synopsis: String,
|
|
||||||
heroImageUrl: String,
|
|
||||||
progress: Double?,
|
|
||||||
watched: Boolean
|
|
||||||
): Movie {
|
|
||||||
return Movie(
|
|
||||||
id = JavaUuid.fromString(id),
|
|
||||||
libraryId = JavaUuid.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
|
||||||
title = title,
|
|
||||||
progress = progress,
|
|
||||||
watched = watched,
|
|
||||||
year = year,
|
|
||||||
rating = rating,
|
|
||||||
runtime = runtime,
|
|
||||||
format = format,
|
|
||||||
synopsis = synopsis,
|
|
||||||
heroImageUrl = heroImageUrl,
|
|
||||||
audioTrack = "English 5.1",
|
|
||||||
subtitles = "English CC",
|
|
||||||
cast = emptyList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewSeries(): Series {
|
|
||||||
return Series(
|
|
||||||
id = JavaUuid.fromString("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"),
|
|
||||||
libraryId = JavaUuid.fromString("cccccccc-cccc-cccc-cccc-cccccccccccc"),
|
|
||||||
name = "Orbital",
|
|
||||||
synopsis = "A reluctant crew returns to a damaged station as political pressure mounts on Earth.",
|
|
||||||
year = "2025",
|
|
||||||
heroImageUrl = "https://images.unsplash.com/photo-1520034475321-cbe63696469a",
|
|
||||||
unwatchedEpisodeCount = 4,
|
|
||||||
seasonCount = 2,
|
|
||||||
seasons = emptyList(),
|
|
||||||
cast = emptyList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun homePreviewEpisode(
|
|
||||||
id: String,
|
|
||||||
title: String,
|
|
||||||
index: Int,
|
|
||||||
releaseDate: String,
|
|
||||||
runtime: String,
|
|
||||||
rating: String,
|
|
||||||
progress: Double?,
|
|
||||||
watched: Boolean,
|
|
||||||
heroImageUrl: String,
|
|
||||||
synopsis: String
|
|
||||||
): Episode {
|
|
||||||
return Episode(
|
|
||||||
id = JavaUuid.fromString(id),
|
|
||||||
seriesId = JavaUuid.fromString("dddddddd-dddd-dddd-dddd-dddddddddddd"),
|
|
||||||
seasonId = JavaUuid.fromString("eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"),
|
|
||||||
index = index,
|
|
||||||
title = title,
|
|
||||||
synopsis = synopsis,
|
|
||||||
releaseDate = releaseDate,
|
|
||||||
rating = rating,
|
|
||||||
runtime = runtime,
|
|
||||||
progress = progress,
|
|
||||||
watched = watched,
|
|
||||||
format = "4K",
|
|
||||||
heroImageUrl = heroImageUrl,
|
|
||||||
cast = emptyList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -25,9 +25,11 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import hu.bbara.purefin.common.ui.components.MediaProgressBar
|
import hu.bbara.purefin.common.ui.components.MediaProgressBar
|
||||||
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
import hu.bbara.purefin.common.ui.components.PurefinAsyncImage
|
||||||
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem
|
import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ContinueWatchingCard(
|
internal fun ContinueWatchingCard(
|
||||||
@@ -51,7 +53,10 @@ internal fun ContinueWatchingCard(
|
|||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
val imageUrl = when (item.type) {
|
val imageUrl = when (item.type) {
|
||||||
BaseItemKind.MOVIE -> item.movie?.heroImageUrl
|
BaseItemKind.MOVIE -> JellyfinImageHelper.finishImageUrl(
|
||||||
|
prefixImageUrl = item.movie?.imageUrlPrefix,
|
||||||
|
imageType = ImageType.PRIMARY
|
||||||
|
)
|
||||||
BaseItemKind.EPISODE -> item.episode?.heroImageUrl
|
BaseItemKind.EPISODE -> item.episode?.heroImageUrl
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -433,10 +433,9 @@ class InMemoryAppContentRepository @Inject constructor(
|
|||||||
runtime = formatRuntime(this.runTimeTicks),
|
runtime = formatRuntime(this.runTimeTicks),
|
||||||
synopsis = this.overview ?: "No synopsis available",
|
synopsis = this.overview ?: "No synopsis available",
|
||||||
format = container?.uppercase() ?: "VIDEO",
|
format = container?.uppercase() ?: "VIDEO",
|
||||||
heroImageUrl = JellyfinImageHelper.toImageUrl(
|
imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl(
|
||||||
url = serverUrl,
|
url = serverUrl,
|
||||||
itemId = this.id,
|
itemId = this.id,
|
||||||
type = ImageType.PRIMARY
|
|
||||||
),
|
),
|
||||||
subtitles = "ENG",
|
subtitles = "ENG",
|
||||||
audioTrack = "ENG",
|
audioTrack = "ENG",
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ class JellyfinImageHelper {
|
|||||||
.toString()
|
.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finishImageUrl(prefixImageUrl: String, imageType: ImageType): String {
|
fun finishImageUrl(prefixImageUrl: String?, imageType: ImageType): String {
|
||||||
|
if (prefixImageUrl.isNullOrEmpty()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return StringBuilder()
|
return StringBuilder()
|
||||||
.append(prefixImageUrl)
|
.append(prefixImageUrl)
|
||||||
.append(imageType.serialName)
|
.append(imageType.serialName)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package hu.bbara.purefin.core.data.room.entity
|
package hu.bbara.purefin.core.data.room.entity
|
||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -21,7 +20,7 @@ data class MovieEntity(
|
|||||||
val runtime: String,
|
val runtime: String,
|
||||||
val format: String,
|
val format: String,
|
||||||
val synopsis: String,
|
val synopsis: String,
|
||||||
val heroImageUrl: String,
|
val imageUrlPrefix: String,
|
||||||
val audioTrack: String,
|
val audioTrack: String,
|
||||||
val subtitles: String
|
val subtitles: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import hu.bbara.purefin.core.data.room.entity.SmartDownloadEntity
|
|||||||
EpisodeEntity::class,
|
EpisodeEntity::class,
|
||||||
SmartDownloadEntity::class,
|
SmartDownloadEntity::class,
|
||||||
],
|
],
|
||||||
version = 6,
|
version = 7,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
@TypeConverters(UuidConverters::class)
|
@TypeConverters(UuidConverters::class)
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ class OfflineRoomMediaLocalDataSource(
|
|||||||
runtime = runtime,
|
runtime = runtime,
|
||||||
format = format,
|
format = format,
|
||||||
synopsis = synopsis,
|
synopsis = synopsis,
|
||||||
heroImageUrl = heroImageUrl,
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
audioTrack = audioTrack,
|
audioTrack = audioTrack,
|
||||||
subtitles = subtitles
|
subtitles = subtitles
|
||||||
)
|
)
|
||||||
@@ -260,7 +260,7 @@ class OfflineRoomMediaLocalDataSource(
|
|||||||
runtime = runtime,
|
runtime = runtime,
|
||||||
format = format,
|
format = format,
|
||||||
synopsis = synopsis,
|
synopsis = synopsis,
|
||||||
heroImageUrl = heroImageUrl,
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
audioTrack = audioTrack,
|
audioTrack = audioTrack,
|
||||||
subtitles = subtitles,
|
subtitles = subtitles,
|
||||||
cast = emptyList()
|
cast = emptyList()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ data class Movie(
|
|||||||
val runtime: String,
|
val runtime: String,
|
||||||
val format: String,
|
val format: String,
|
||||||
val synopsis: String,
|
val synopsis: String,
|
||||||
val heroImageUrl: String,
|
val imageUrlPrefix: String,
|
||||||
val audioTrack: String,
|
val audioTrack: String,
|
||||||
val subtitles: String,
|
val subtitles: String,
|
||||||
val cast: List<CastMember>
|
val cast: List<CastMember>
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ class PlayerMediaRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
private suspend fun buildOfflineMediaItem(mediaId: UUID): Pair<MediaItem, Long?>? {
|
private suspend fun buildOfflineMediaItem(mediaId: UUID): Pair<MediaItem, Long?>? {
|
||||||
val download = downloadManager.downloadIndex.getDownload(mediaId.toString())
|
val download = downloadManager.downloadIndex.getDownload(mediaId.toString())
|
||||||
?.takeIf { it.state == Download.STATE_COMPLETED }
|
?.takeIf { it.state == Download.STATE_COMPLETED }
|
||||||
@@ -138,7 +139,7 @@ class PlayerMediaRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
val subtitle = episode?.let { episodeSubtitle(null, it.index) }
|
val subtitle = episode?.let { episodeSubtitle(null, it.index) }
|
||||||
val artworkUrl = when {
|
val artworkUrl = when {
|
||||||
movie != null -> movie.heroImageUrl
|
movie != null -> JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.PRIMARY)
|
||||||
episode != null -> episode.heroImageUrl
|
episode != null -> episode.heroImageUrl
|
||||||
else -> JellyfinImageHelper.toImageUrl(serverUrl, mediaId, ImageType.PRIMARY)
|
else -> JellyfinImageHelper.toImageUrl(serverUrl, mediaId, ImageType.PRIMARY)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import hu.bbara.purefin.core.model.Season
|
|||||||
import hu.bbara.purefin.core.model.Series
|
import hu.bbara.purefin.core.model.Series
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -32,6 +31,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
@@ -156,10 +156,9 @@ class MediaDownloadManager @Inject constructor(
|
|||||||
runtime = formatRuntime(itemInfo.runTimeTicks),
|
runtime = formatRuntime(itemInfo.runTimeTicks),
|
||||||
synopsis = itemInfo.overview ?: "No synopsis available",
|
synopsis = itemInfo.overview ?: "No synopsis available",
|
||||||
format = itemInfo.container?.uppercase() ?: "VIDEO",
|
format = itemInfo.container?.uppercase() ?: "VIDEO",
|
||||||
heroImageUrl = JellyfinImageHelper.toImageUrl(
|
imageUrlPrefix = JellyfinImageHelper.toPrefixImageUrl(
|
||||||
url = serverUrl,
|
url = serverUrl,
|
||||||
itemId = itemInfo.id,
|
itemId = itemInfo.id
|
||||||
type = ImageType.PRIMARY
|
|
||||||
),
|
),
|
||||||
subtitles = "ENG",
|
subtitles = "ENG",
|
||||||
audioTrack = "ENG",
|
audioTrack = "ENG",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import hu.bbara.purefin.core.data.OfflineMediaRepository
|
import hu.bbara.purefin.core.data.OfflineMediaRepository
|
||||||
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.core.data.navigation.MovieDto
|
import hu.bbara.purefin.core.data.navigation.MovieDto
|
||||||
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
import hu.bbara.purefin.core.data.navigation.NavigationManager
|
||||||
import hu.bbara.purefin.core.data.navigation.Route
|
import hu.bbara.purefin.core.data.navigation.Route
|
||||||
@@ -16,6 +17,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -70,7 +72,7 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
contentId = contentId,
|
contentId = contentId,
|
||||||
title = movie.title,
|
title = movie.title,
|
||||||
subtitle = "",
|
subtitle = "",
|
||||||
imageUrl = movie.heroImageUrl,
|
imageUrl = JellyfinImageHelper.finishImageUrl(movie.imageUrlPrefix, ImageType.PRIMARY),
|
||||||
progress = progress
|
progress = progress
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package hu.bbara.purefin.feature.shared.home
|
package hu.bbara.purefin.feature.shared.home
|
||||||
|
|
||||||
|
import hu.bbara.purefin.core.data.image.JellyfinImageHelper
|
||||||
import hu.bbara.purefin.core.model.Episode
|
import hu.bbara.purefin.core.model.Episode
|
||||||
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 org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
|
||||||
|
|
||||||
sealed interface SuggestedItem {
|
sealed interface SuggestedItem {
|
||||||
@@ -74,7 +76,10 @@ data class SuggestedMovie (
|
|||||||
override val id: UUID = movie.id,
|
override val id: UUID = movie.id,
|
||||||
override val title: String = movie.title,
|
override val title: String = movie.title,
|
||||||
override val description: String = movie.synopsis,
|
override val description: String = movie.synopsis,
|
||||||
override val imageUrl: String = movie.heroImageUrl
|
override val imageUrl: String = JellyfinImageHelper.finishImageUrl(
|
||||||
|
prefixImageUrl = movie.imageUrlPrefix,
|
||||||
|
imageType = ImageType.PRIMARY
|
||||||
|
)
|
||||||
) : SuggestedItem
|
) : SuggestedItem
|
||||||
|
|
||||||
data class ContinueWatchingItem(
|
data class ContinueWatchingItem(
|
||||||
@@ -139,7 +144,7 @@ data class PosterItem(
|
|||||||
else -> throw IllegalArgumentException("Invalid type: $type")
|
else -> throw IllegalArgumentException("Invalid type: $type")
|
||||||
}
|
}
|
||||||
val imageUrl: String = when (type) {
|
val imageUrl: String = when (type) {
|
||||||
BaseItemKind.MOVIE -> movie!!.heroImageUrl
|
BaseItemKind.MOVIE -> movie!!.imageUrlPrefix
|
||||||
BaseItemKind.EPISODE -> episode!!.heroImageUrl
|
BaseItemKind.EPISODE -> episode!!.heroImageUrl
|
||||||
BaseItemKind.SERIES -> series!!.heroImageUrl
|
BaseItemKind.SERIES -> series!!.heroImageUrl
|
||||||
else -> throw IllegalArgumentException("Invalid type: $type")
|
else -> throw IllegalArgumentException("Invalid type: $type")
|
||||||
|
|||||||
Reference in New Issue
Block a user