diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt index 3367e72d..c5d02b37 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt @@ -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(), ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/BackdropService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/BackdropService.kt index cdbb7a2a..a64fc9d9 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/BackdropService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/BackdropService.kt @@ -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,27 +47,38 @@ class BackdropService suspend fun submit(item: BaseItem) = withContext(Dispatchers.IO) { - val imageUrl = imageUrlService.getItemImageUrl(item, ImageType.BACKDROP) - if (backdropFlow.firstOrNull()?.imageUrl != imageUrl) { - _backdropFlow.update { - it.copy( - itemId = item.id, - imageUrl = null, - ) - } - extractColors(item) - } + 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 = itemId, + imageUrl = null, + ) + } + extractColors(itemId, imageUrl) + } + } + suspend fun clearBackdrop() { _backdropFlow.update { BackdropResult.NONE } } - 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, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/DiscoverItemCard.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/DiscoverItemCard.kt index 740481ae..d6cb7324 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/cards/DiscoverItemCard.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/cards/DiscoverItemCard.kt @@ -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, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt index aeaaefe9..c4a33928 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt @@ -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 diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt index 784729ba..eaa51fee 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt @@ -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>(listOf()) val discoverMovies = MutableLiveData>(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 -> - viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(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 -> - viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(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) + } + }, ) } }