Movie details improvements

This commit is contained in:
Damontecres 2025-12-07 22:36:22 -05:00
parent 69fb26a070
commit 5738d4732d
No known key found for this signature in database
3 changed files with 86 additions and 77 deletions

View file

@ -36,8 +36,7 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
import com.github.damontecres.wholphin.data.ExtrasItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.DiscoverRating
import com.github.damontecres.wholphin.data.model.LocalTrailer import com.github.damontecres.wholphin.data.model.LocalTrailer
import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.data.model.Person
@ -46,7 +45,7 @@ import com.github.damontecres.wholphin.data.model.Trailer
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.TrailerService import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.cards.ExtrasRow import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.cards.PersonRow import com.github.damontecres.wholphin.ui.cards.PersonRow
import com.github.damontecres.wholphin.ui.cards.SeasonCard import com.github.damontecres.wholphin.ui.cards.SeasonCard
@ -59,7 +58,6 @@ import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.rememberInt
@ -88,8 +86,8 @@ fun DiscoverMovieDetails(
val rating by viewModel.rating.observeAsState(null) val rating by viewModel.rating.observeAsState(null)
val people by viewModel.people.observeAsState(listOf()) val people by viewModel.people.observeAsState(listOf())
val trailers by viewModel.trailers.observeAsState(listOf()) val trailers by viewModel.trailers.observeAsState(listOf())
val extras by viewModel.extras.observeAsState(listOf())
val similar by viewModel.similar.observeAsState(listOf()) val similar by viewModel.similar.observeAsState(listOf())
val recommended by viewModel.similar.observeAsState(listOf())
val loading by viewModel.loading.observeAsState(LoadingState.Loading) val loading by viewModel.loading.observeAsState(LoadingState.Loading)
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) } var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
@ -121,14 +119,14 @@ fun DiscoverMovieDetails(
movie = movie, movie = movie,
rating = rating, rating = rating,
people = people, people = people,
extras = extras,
trailers = trailers, trailers = trailers,
similar = similar, similar = similar,
recommended = recommended,
requestOnClick = { requestOnClick = {
// TODO seerr // TODO seerr
}, },
onClickItem = { index, item -> onClickItem = { index, item ->
viewModel.navigateTo(item.destination()) viewModel.navigateTo(Destination.DiscoveredItem(item))
}, },
onClickPerson = { onClickPerson = {
}, },
@ -164,29 +162,10 @@ fun DiscoverMovieDetails(
) )
}, },
onLongClickSimilar = { index, similar -> onLongClickSimilar = { index, similar ->
val items =
buildMoreDialogItemsForHome(
context = context,
item = similar,
seriesId = null,
playbackPosition = similar.playbackPosition,
watched = similar.played,
favorite = similar.favorite,
actions = moreActions,
)
moreDialog =
DialogParams(
fromLongClick = true,
title = similar.title ?: "",
items = items,
)
}, },
trailerOnClick = { trailerOnClick = {
TrailerService.onClick(context, it, viewModel::navigateTo) TrailerService.onClick(context, it, viewModel::navigateTo)
}, },
onClickExtra = { index, extra ->
viewModel.navigateTo(extra.destination)
},
modifier = modifier, modifier = modifier,
) )
} }
@ -217,6 +196,7 @@ private const val TRAILER_ROW = PEOPLE_ROW + 1
private const val CHAPTER_ROW = TRAILER_ROW + 1 private const val CHAPTER_ROW = TRAILER_ROW + 1
private const val EXTRAS_ROW = CHAPTER_ROW + 1 private const val EXTRAS_ROW = CHAPTER_ROW + 1
private const val SIMILAR_ROW = EXTRAS_ROW + 1 private const val SIMILAR_ROW = EXTRAS_ROW + 1
private const val RECOMMENDED_ROW = SIMILAR_ROW + 1
@Composable @Composable
fun DiscoverMovieDetailsContent( fun DiscoverMovieDetailsContent(
@ -225,23 +205,22 @@ fun DiscoverMovieDetailsContent(
rating: DiscoverRating?, rating: DiscoverRating?,
people: List<Person>, people: List<Person>,
trailers: List<Trailer>, trailers: List<Trailer>,
extras: List<ExtrasItem>, similar: List<DiscoverItem>,
similar: List<BaseItem>, recommended: List<DiscoverItem>,
requestOnClick: () -> Unit, requestOnClick: () -> Unit,
trailerOnClick: (Trailer) -> Unit, trailerOnClick: (Trailer) -> Unit,
overviewOnClick: () -> Unit, overviewOnClick: () -> Unit,
moreOnClick: () -> Unit, moreOnClick: () -> Unit,
onClickItem: (Int, BaseItem) -> Unit, onClickItem: (Int, DiscoverItem) -> Unit,
onClickPerson: (Person) -> Unit, onClickPerson: (Person) -> Unit,
onLongClickPerson: (Int, Person) -> Unit, onLongClickPerson: (Int, Person) -> Unit,
onLongClickSimilar: (Int, BaseItem) -> Unit, onLongClickSimilar: (Int, DiscoverItem) -> Unit,
onClickExtra: (Int, ExtrasItem) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var position by rememberInt(0) var position by rememberInt(0)
val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } } val focusRequesters = remember { List(RECOMMENDED_ROW + 1) { FocusRequester() } }
val bringIntoViewRequester = remember { BringIntoViewRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -329,22 +308,6 @@ fun DiscoverMovieDetailsContent(
} }
} }
if (extras.isNotEmpty()) {
item {
ExtrasRow(
extras = extras,
onClickItem = { index, item ->
position = EXTRAS_ROW
onClickExtra.invoke(index, item)
},
onLongClickItem = { _, _ -> },
modifier =
Modifier
.fillMaxWidth()
.focusRequester(focusRequesters[EXTRAS_ROW]),
)
}
}
if (similar.isNotEmpty()) { if (similar.isNotEmpty()) {
item { item {
ItemRow( ItemRow(
@ -359,14 +322,11 @@ fun DiscoverMovieDetailsContent(
onLongClickSimilar.invoke(index, similar) onLongClickSimilar.invoke(index, similar)
}, },
cardContent = { index, item, mod, onClick, onLongClick -> cardContent = { index, item, mod, onClick, onLongClick ->
SeasonCard( DiscoverItemCard(
item = item, item = item,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
modifier = mod, modifier = mod,
showImageOverlay = true,
imageHeight = Cards.height2x3,
imageWidth = Dp.Unspecified,
) )
}, },
modifier = modifier =
@ -376,6 +336,34 @@ fun DiscoverMovieDetailsContent(
) )
} }
} }
if (recommended.isNotEmpty()) {
item {
ItemRow(
title = stringResource(R.string.recommended),
items = similar,
onClickItem = { index, item ->
position = RECOMMENDED_ROW
onClickItem.invoke(index, item)
},
onLongClickItem = { index, similar ->
position = RECOMMENDED_ROW
onLongClickSimilar.invoke(index, similar)
},
cardContent = { index, item, mod, onClick, onLongClick ->
DiscoverItemCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
modifier = mod,
)
},
modifier =
Modifier
.fillMaxWidth()
.focusRequester(focusRequesters[RECOMMENDED_ROW]),
)
}
}
} }
} }
} }

View file

@ -29,6 +29,7 @@ import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@Composable @Composable
@ -71,9 +72,18 @@ fun DiscoverMovieDetailsHeader(
?.roundMinutes ?.roundMinutes
?.toString() ?.toString()
?.let(::add) ?.let(::add)
movie.releases val release =
?.results movie.releases
?.firstOrNull() ?.results
?.firstOrNull { it.iso31661 == Locale.getDefault().country }
?: movie.releases
?.results
?.firstOrNull { it.iso31661 == Locale.US.country }
?: movie.releases
?.results
?.firstOrNull()
release
?.releaseDates ?.releaseDates
?.firstOrNull() ?.firstOrNull()
?.certification ?.certification

View file

@ -5,17 +5,16 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.api.seerr.model.MovieDetails import com.github.damontecres.wholphin.api.seerr.model.MovieDetails
import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo
import com.github.damontecres.wholphin.data.ExtrasItem
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.Chapter
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.DiscoverRating
import com.github.damontecres.wholphin.data.model.Person 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.data.model.Trailer
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.SeerrService import com.github.damontecres.wholphin.services.SeerrService
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.setValueOnMain
@ -58,10 +57,8 @@ class DiscoverMovieViewModel
val trailers = MutableLiveData<List<Trailer>>(listOf()) val trailers = MutableLiveData<List<Trailer>>(listOf())
val people = MutableLiveData<List<Person>>(listOf()) val people = MutableLiveData<List<Person>>(listOf())
val chapters = MutableLiveData<List<Chapter>>(listOf()) val similar = MutableLiveData<List<DiscoverItem>>(listOf())
val extras = MutableLiveData<List<ExtrasItem>>(listOf()) val recommended = MutableLiveData<List<DiscoverItem>>(listOf())
val similar = MutableLiveData<List<BaseItem>>()
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
init { init {
init() init()
@ -96,6 +93,26 @@ class DiscoverMovieViewModel
val result = seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = item.id) val result = seerrService.api.moviesApi.movieMovieIdRatingsGet(movieId = item.id)
rating.setValueOnMain(DiscoverRating(result)) rating.setValueOnMain(DiscoverRating(result))
} }
if (!similar.isInitialized) {
viewModelScope.launchIO {
val result =
seerrService.api.moviesApi
.movieMovieIdSimilarGet(movieId = item.id, page = 2)
.results
?.map(::DiscoverItem)
.orEmpty()
similar.setValueOnMain(result)
}
viewModelScope.launchIO {
val result =
seerrService.api.moviesApi
.movieMovieIdRecommendationsGet(movieId = item.id, page = 2)
.results
?.map(::DiscoverItem)
.orEmpty()
similar.setValueOnMain(result)
}
}
val people = val people =
movie.credits movie.credits
?.cast ?.cast
@ -122,20 +139,14 @@ class DiscoverMovieViewModel
) )
}.orEmpty() }.orEmpty()
this@DiscoverMovieViewModel.people.setValueOnMain(people) this@DiscoverMovieViewModel.people.setValueOnMain(people)
if (!similar.isInitialized) { val trailers =
// val similar = movie.relatedVideos
// api.libraryApi ?.filter { it.type == RelatedVideo.Type.TRAILER }
// .getSimilarItems( ?.filter { it.name.isNotNullOrBlank() && it.url.isNotNullOrBlank() }
// GetSimilarItemsRequest( ?.map {
// userId = serverRepository.currentUser.value?.id, RemoteTrailer(it.name!!, it.url!!)
// itemId = itemId, }.orEmpty()
// fields = SlimItemFields, this@DiscoverMovieViewModel.trailers.setValueOnMain(trailers)
// limit = 25,
// ),
// ).content.items
// .map { BaseItem.Companion.from(it, api) }
// this@DiscoverMovieViewModel.similar.setValueOnMain(similar)
}
} }
fun navigateTo(destination: Destination) { fun navigateTo(destination: Destination) {