mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Add backdrops to discover pages
This commit is contained in:
parent
f27415de0f
commit
22348a5adc
5 changed files with 111 additions and 22 deletions
|
|
@ -1,11 +1,18 @@
|
|||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieMovieIdRatingsGet200Response
|
||||
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
|
||||
import com.github.damontecres.wholphin.api.seerr.model.TvResult
|
||||
import com.github.damontecres.wholphin.services.SeerrSearchResult
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
enum class SeerrItemType {
|
||||
|
|
@ -59,7 +66,11 @@ data class DiscoverItem(
|
|||
val releaseDate: String?,
|
||||
val posterPath: String?,
|
||||
val backdropPath: String?,
|
||||
val jellyfinItemId: UUID?,
|
||||
) {
|
||||
val backDropUrl: String? get() = backdropPath?.let { "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces$it" }
|
||||
val posterUrl: String? get() = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }
|
||||
|
||||
constructor(movie: MovieResult) : this(
|
||||
id = movie.id,
|
||||
type = SeerrItemType.MOVIE,
|
||||
|
|
@ -68,6 +79,18 @@ data class DiscoverItem(
|
|||
releaseDate = movie.releaseDate,
|
||||
posterPath = movie.posterPath,
|
||||
backdropPath = movie.backdropPath,
|
||||
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(movie: MovieDetails) : this(
|
||||
id = movie.id ?: -1,
|
||||
type = SeerrItemType.MOVIE,
|
||||
title = movie.title,
|
||||
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
|
||||
releaseDate = movie.releaseDate,
|
||||
posterPath = movie.posterPath,
|
||||
backdropPath = movie.backdropPath,
|
||||
jellyfinItemId = movie.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(tv: TvResult) : this(
|
||||
|
|
@ -78,6 +101,7 @@ data class DiscoverItem(
|
|||
releaseDate = tv.firstAirDate,
|
||||
posterPath = tv.posterPath,
|
||||
backdropPath = tv.backdropPath,
|
||||
jellyfinItemId = tv.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
|
||||
constructor(search: SeerrSearchResult) : this(
|
||||
|
|
@ -90,6 +114,7 @@ data class DiscoverItem(
|
|||
releaseDate = search.releaseDate ?: search.firstAirDate,
|
||||
posterPath = search.posterPath,
|
||||
backdropPath = search.backdropPath,
|
||||
jellyfinItemId = search.mediaInfo?.jellyfinMediaId?.toUUIDOrNull(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import coil3.request.SuccessResult
|
|||
import coil3.request.allowHardware
|
||||
import coil3.request.bitmapConfig
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.BackdropStyle
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
|
|
@ -27,7 +28,6 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -47,15 +47,24 @@ class BackdropService
|
|||
|
||||
suspend fun submit(item: BaseItem) =
|
||||
withContext(Dispatchers.IO) {
|
||||
val imageUrl = imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)
|
||||
val imageUrl = imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)!!
|
||||
submit(item.id.toString(), imageUrl)
|
||||
}
|
||||
|
||||
suspend fun submit(item: DiscoverItem) = submit("discover_${item.id}", item.backDropUrl)
|
||||
|
||||
suspend fun submit(
|
||||
itemId: String,
|
||||
imageUrl: String?,
|
||||
) = withContext(Dispatchers.IO) {
|
||||
if (backdropFlow.firstOrNull()?.imageUrl != imageUrl) {
|
||||
_backdropFlow.update {
|
||||
it.copy(
|
||||
itemId = item.id,
|
||||
itemId = itemId,
|
||||
imageUrl = null,
|
||||
)
|
||||
}
|
||||
extractColors(item)
|
||||
extractColors(itemId, imageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,9 +74,11 @@ class BackdropService
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun extractColors(item: BaseItem) {
|
||||
private suspend fun extractColors(
|
||||
itemId: String,
|
||||
imageUrl: String?,
|
||||
) {
|
||||
delay(500)
|
||||
val imageUrl = imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)
|
||||
val backdropStyle =
|
||||
preferences.data
|
||||
.firstOrNull()
|
||||
|
|
@ -83,9 +94,9 @@ class BackdropService
|
|||
ExtractedColors.DEFAULT
|
||||
}
|
||||
_backdropFlow.update {
|
||||
if (it.itemId == item.id) {
|
||||
if (it.itemId == itemId) {
|
||||
BackdropResult(
|
||||
itemId = item.id,
|
||||
itemId = itemId,
|
||||
imageUrl = imageUrl,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor,
|
||||
|
|
@ -208,7 +219,7 @@ class BackdropService
|
|||
}
|
||||
|
||||
data class BackdropResult(
|
||||
val itemId: UUID?,
|
||||
val itemId: String?,
|
||||
val imageUrl: String?,
|
||||
val primaryColor: Color = Color.Unspecified,
|
||||
val secondaryColor: Color = Color.Unspecified,
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ fun DiscoverItemCard(
|
|||
.fillMaxSize(),
|
||||
) {
|
||||
ItemCardImage(
|
||||
imageUrl = item?.posterPath?.let { "https://image.tmdb.org/t/p/w500$it" },
|
||||
imageUrl = item?.posterUrl,
|
||||
name = item?.title,
|
||||
showOverlay = false,
|
||||
favorite = false,
|
||||
|
|
|
|||
|
|
@ -89,8 +89,8 @@ class DiscoverMovieViewModel
|
|||
),
|
||||
) {
|
||||
val movie = fetchAndSetItem().await()
|
||||
// TODO backdrop url = "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/$it"
|
||||
// backdropService.submit(movie)
|
||||
val discoveredItem = DiscoverItem(movie)
|
||||
backdropService.submit(discoveredItem)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
loading.value = LoadingState.Success
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
|
|
@ -23,16 +24,21 @@ import com.github.damontecres.wholphin.R
|
|||
import com.github.damontecres.wholphin.api.seerr.MediaApi
|
||||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SeerrService
|
||||
import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -42,6 +48,7 @@ class SeerrDiscoverViewModel
|
|||
private val seerrService: SeerrService,
|
||||
val navigationManager: NavigationManager,
|
||||
private val api: ApiClient,
|
||||
private val backdropService: BackdropService,
|
||||
) : ViewModel() {
|
||||
val recentlyAdded = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val discoverMovies = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
|
|
@ -49,7 +56,7 @@ class SeerrDiscoverViewModel
|
|||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
|
||||
backdropService.clearBackdrop()
|
||||
val tv =
|
||||
seerrService.api.mediaApi
|
||||
.mediaGet(
|
||||
|
|
@ -69,6 +76,14 @@ class SeerrDiscoverViewModel
|
|||
discoverTv.setValueOnMain(tv)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBackdrop(item: DiscoverItem?) {
|
||||
viewModelScope.launchIO {
|
||||
if (item != null) {
|
||||
backdropService.submit("discover_${item.id}", item.backDropUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -83,6 +98,16 @@ fun SeerrDiscoverPage(
|
|||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(movies) { if (movies.isNotEmpty()) focusRequester.tryRequestFocus() }
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
var position by rememberPosition(0, 0)
|
||||
LaunchedEffect(position) {
|
||||
position.let {
|
||||
val item = if (it.row == 0) movies.getOrNull(it.column) else tv.getOrNull(it.column)
|
||||
Timber.v("Backdrop for $item")
|
||||
viewModel.updateBackdrop(item)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
|
|
@ -94,7 +119,16 @@ fun SeerrDiscoverPage(
|
|||
title = stringResource(R.string.movies),
|
||||
items = movies,
|
||||
onClickItem = { index, item ->
|
||||
if (item.jellyfinItemId != null) {
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.MediaItem(
|
||||
itemId = item.jellyfinItemId,
|
||||
type = BaseItemKind.MOVIE,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item))
|
||||
}
|
||||
},
|
||||
onLongClickItem = { index, item -> },
|
||||
cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -106,13 +140,27 @@ fun SeerrDiscoverPage(
|
|||
modifier = mod,
|
||||
)
|
||||
},
|
||||
cardOnFocus = { isFocused, index ->
|
||||
if (isFocused) {
|
||||
position = RowColumn(0, index)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
)
|
||||
ItemRow(
|
||||
title = stringResource(R.string.tv_shows),
|
||||
items = tv,
|
||||
onClickItem = { index, item ->
|
||||
if (item.jellyfinItemId != null) {
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.MediaItem(
|
||||
itemId = item.jellyfinItemId,
|
||||
type = BaseItemKind.SERIES,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item))
|
||||
}
|
||||
},
|
||||
onLongClickItem = { index, item -> },
|
||||
cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -124,6 +172,11 @@ fun SeerrDiscoverPage(
|
|||
modifier = mod,
|
||||
)
|
||||
},
|
||||
cardOnFocus = { isFocused, index ->
|
||||
if (isFocused) {
|
||||
position = RowColumn(1, index)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue