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 0a3a2c8b..3367e72d 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 @@ -3,13 +3,34 @@ package com.github.damontecres.wholphin.data.model 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 @Serializable enum class SeerrItemType { + @SerialName("movie") MOVIE, + + @SerialName("tv") TV, + + @SerialName("person") PERSON, + + @SerialName("unknown") + UNKNOWN, + ; + + companion object { + fun fromString(str: String?) = + when (str) { + "movie" -> MOVIE + "tv" -> TV + "person" -> PERSON + else -> UNKNOWN + } + } } @Serializable @@ -58,6 +79,18 @@ data class DiscoverItem( posterPath = tv.posterPath, backdropPath = tv.backdropPath, ) + + constructor(search: SeerrSearchResult) : this( + id = search.id, + type = SeerrItemType.fromString(search.mediaType), + title = search.title ?: search.name, + availability = + SeerrAvailability.from(search.mediaInfo?.status) + ?: SeerrAvailability.UNKNOWN, + releaseDate = search.releaseDate ?: search.firstAirDate, + posterPath = search.posterPath, + backdropPath = search.backdropPath, + ) } data class DiscoverRating( 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 ac77d7a8..740481ae 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 @@ -1,12 +1,43 @@ package com.github.damontecres.wholphin.ui.cards +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.NonRestartableComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.tv.material3.Card +import androidx.tv.material3.CardDefaults +import androidx.tv.material3.Text +import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.DiscoverItem +import com.github.damontecres.wholphin.data.model.SeerrItemType +import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.Cards +import com.github.damontecres.wholphin.ui.enableMarquee +import kotlinx.coroutines.delay @Composable @NonRestartableComposable @@ -14,20 +45,125 @@ fun DiscoverItemCard( item: DiscoverItem?, onClick: () -> Unit, onLongClick: () -> Unit, + showOverlay: Boolean, modifier: Modifier = Modifier, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, -) = SeasonCard( - title = item?.title, - subtitle = item?.releaseDate, - name = item?.title, - imageUrl = item?.posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }, // TODO - isFavorite = false, - isPlayed = false, - unplayedItemCount = -1, - playedPercentage = -1.0, - onClick = onClick, - onLongClick = onLongClick, - imageHeight = Cards.height2x3, - modifier = modifier, - interactionSource = interactionSource, -) +) { + val focused by interactionSource.collectIsFocusedAsState() + val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp) + val spaceBelow by animateDpAsState(if (focused) 4.dp else 12.dp) + var focusedAfterDelay by remember { mutableStateOf(false) } + + val hideOverlayDelay = 500L + if (focused) { + LaunchedEffect(Unit) { + delay(hideOverlayDelay) + if (focused) { + focusedAfterDelay = true + } else { + focusedAfterDelay = false + } + } + } else { + focusedAfterDelay = false + } + val width = Cards.height2x3 * AspectRatios.TALL + val height = Dp.Unspecified * (1f / AspectRatios.TALL) + Column( + verticalArrangement = Arrangement.spacedBy(spaceBetween), + modifier = modifier.size(width, height), + ) { + Card( + modifier = + Modifier + .size(Dp.Unspecified, Cards.height2x3) + .aspectRatio(AspectRatios.TALL), + onClick = onClick, + onLongClick = onLongClick, + interactionSource = interactionSource, + colors = + CardDefaults.colors( + containerColor = Color.Transparent, + ), + ) { + Box( + modifier = + Modifier + .fillMaxSize(), + ) { + ItemCardImage( + imageUrl = item?.posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }, + name = item?.title, + showOverlay = false, + favorite = false, + watched = false, + unwatchedCount = 0, + watchedPercent = null, + useFallbackText = false, + contentScale = ContentScale.FillBounds, + modifier = + Modifier + .fillMaxSize(), + ) + if (showOverlay) { + // TODO better colors + val color = + when (item?.type) { + SeerrItemType.MOVIE -> Color.Blue + SeerrItemType.TV -> Color.Red + SeerrItemType.PERSON -> Color.Green + SeerrItemType.UNKNOWN -> Color.Black + null -> Color.Black + }.copy(alpha = .5f) + when (item?.type) { + SeerrItemType.MOVIE -> R.string.movies + SeerrItemType.TV -> R.string.tv_shows + SeerrItemType.PERSON -> R.string.people + SeerrItemType.UNKNOWN -> null + null -> null + }?.let { + Text( + text = stringResource(it), + modifier = + Modifier + .align(Alignment.TopStart) + .padding(4.dp) + .background( + color = color, + shape = CircleShape, + ).padding(4.dp), + ) + } + } + } + } + Column( + verticalArrangement = Arrangement.spacedBy(0.dp), + modifier = + Modifier + .padding(bottom = spaceBelow) + .fillMaxWidth(), + ) { + Text( + text = item?.title ?: "", + maxLines = 1, + textAlign = TextAlign.Center, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp) + .enableMarquee(focusedAfterDelay), + ) + Text( + text = item?.releaseDate ?: "", + maxLines = 1, + textAlign = TextAlign.Center, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp) + .enableMarquee(focusedAfterDelay), + ) + } + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt index 654228ff..a864bb0d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt @@ -49,7 +49,6 @@ import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.PersonRow import com.github.damontecres.wholphin.ui.cards.SeasonCard -import com.github.damontecres.wholphin.ui.components.DetailsBackdropImage import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.ErrorMessage @@ -227,9 +226,6 @@ fun DiscoverMovieDetailsContent( focusRequesters.getOrNull(position)?.tryRequestFocus() } Box(modifier = modifier) { - movie.backdropPath?.let { - DetailsBackdropImage("https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/$it") - } LazyColumn( verticalArrangement = Arrangement.spacedBy(16.dp), contentPadding = PaddingValues(horizontal = 32.dp, vertical = 8.dp), @@ -326,6 +322,7 @@ fun DiscoverMovieDetailsContent( item = item, onClick = onClick, onLongClick = onLongClick, + showOverlay = false, modifier = mod, ) }, @@ -354,6 +351,7 @@ fun DiscoverMovieDetailsContent( item = item, onClick = onClick, onLongClick = onLongClick, + showOverlay = true, modifier = mod, ) }, 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 ad8193c2..440e6d87 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 @@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.data.model.RemoteTrailer import com.github.damontecres.wholphin.data.model.Trailer +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.isNotNullOrBlank @@ -42,6 +43,7 @@ class DiscoverMovieViewModel private val api: ApiClient, @param:ApplicationContext private val context: Context, private val navigationManager: NavigationManager, + private val backdropService: BackdropService, val serverRepository: ServerRepository, val seerrService: SeerrService, @Assisted val item: DiscoverItem, @@ -86,6 +88,9 @@ class DiscoverMovieViewModel ), ) { val movie = fetchAndSetItem().await() + // TODO backdrop url = "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/$it" +// backdropService.submit(movie) + withContext(Dispatchers.Main) { loading.value = LoadingState.Success } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt index 602e2a46..07bf93e6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/SearchPage.kt @@ -39,12 +39,14 @@ import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.BaseItem +import com.github.damontecres.wholphin.data.model.DiscoverItem +import com.github.damontecres.wholphin.data.model.SeerrItemType import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.NavigationManager -import com.github.damontecres.wholphin.services.SeerrSearchResult import com.github.damontecres.wholphin.services.SeerrService import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.SlimItemFields +import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard import com.github.damontecres.wholphin.ui.cards.EpisodeCard import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.SeasonCard @@ -144,7 +146,11 @@ class SearchViewModel if (seerrService.active) { viewModelScope.launchIO { seerrResults.setValueOnMain(SearchResult.Searching) - val results = seerrService.search(query) + val results = + seerrService + .search(query) + .map { DiscoverItem(it) } + .filter { it.type == SeerrItemType.MOVIE || it.type == SeerrItemType.TV } seerrResults.setValueOnMain(SearchResult.SuccessSeerr(results)) } } @@ -170,7 +176,7 @@ sealed interface SearchResult { ) : SearchResult data class SuccessSeerr( - val items: List, + val items: List, ) : SearchResult } @@ -416,19 +422,12 @@ fun LazyListScope.searchResultRow( onClickItem = { index, item -> }, onLongClickItem = { _, _ -> }, modifier = modifier, - cardContent = { index: Int, item: SeerrSearchResult?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> - SeasonCard( - title = item?.title ?: item?.name, - subtitle = null, - name = item?.title ?: item?.name, - imageUrl = item?.posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }, // TODO - isFavorite = false, - isPlayed = false, - unplayedItemCount = -1, - playedPercentage = -1.0, + cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> + DiscoverItemCard( + item = item, onClick = onClick, onLongClick = onLongClick, - imageHeight = Cards.height2x3, + showOverlay = true, modifier = mod, ) }, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt index 182ec610..d6013d56 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt @@ -284,6 +284,10 @@ fun DestinationContent( SeerrItemType.PERSON -> { TODO() } + + SeerrItemType.UNKNOWN -> { + TODO() + } } } } 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 2e3d33a4..89cd0aee 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 @@ -9,11 +9,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +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 @@ -83,7 +85,7 @@ fun SeerrDiscoverPage( .padding(16.dp), ) { ItemRow( - title = "Movies", + title = stringResource(R.string.movies), items = movies, onClickItem = { index, item -> viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item)) @@ -94,12 +96,13 @@ fun SeerrDiscoverPage( item = item, onClick = onClick, onLongClick = onLongClick, + showOverlay = false, modifier = mod, ) }, ) ItemRow( - title = "TV", + title = stringResource(R.string.tv_shows), items = tv, onClickItem = { index, item -> viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item)) @@ -110,6 +113,7 @@ fun SeerrDiscoverPage( item = item, onClick = onClick, onLongClick = onLongClick, + showOverlay = false, modifier = mod, ) },