mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Show local & remote trailers for TV Shows (#212)
Shows local & remote trailers for TV Shows similar to how its shown for Movies Closes #141
This commit is contained in:
parent
f61e819a98
commit
fe29b0cd88
5 changed files with 126 additions and 46 deletions
|
|
@ -1,6 +1,5 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.movie
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -82,6 +81,7 @@ import com.github.damontecres.wholphin.ui.rememberInt
|
|||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.TrailerService
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
|
|
@ -277,22 +277,8 @@ fun MovieDetails(
|
|||
items = items,
|
||||
)
|
||||
},
|
||||
trailerOnClick = { trailer ->
|
||||
when (trailer) {
|
||||
is LocalTrailer ->
|
||||
viewModel.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId = trailer.baseItem.id,
|
||||
item = trailer.baseItem,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
|
||||
is RemoteTrailer -> {
|
||||
val intent = Intent(Intent.ACTION_VIEW, trailer.url.toUri())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
trailerOnClick = {
|
||||
TrailerService.onClick(context, it, viewModel::navigateTo)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
@ -567,7 +553,7 @@ fun TrailerRow(
|
|||
LazyRow(
|
||||
state = state,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -4,16 +4,13 @@ import android.content.Context
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||
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.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.LocalTrailer
|
||||
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.preferences.ThemeSongVolume
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
|
|
@ -27,6 +24,7 @@ import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.PeopleFavorites
|
||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.util.TrailerService
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -57,6 +55,7 @@ class MovieViewModel
|
|||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val peopleFavorites: PeopleFavorites,
|
||||
private val trailerService: TrailerService,
|
||||
@Assisted val itemId: UUID,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
|
|
@ -108,31 +107,9 @@ class MovieViewModel
|
|||
loading.value = LoadingState.Success
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val remoteTrailers =
|
||||
item.data.remoteTrailers
|
||||
?.mapNotNull { t ->
|
||||
t.url?.let { url ->
|
||||
val name =
|
||||
t.name
|
||||
// TODO would be nice to clean up the trailer name
|
||||
// ?.replace(item.name ?: "", "")
|
||||
// ?.removePrefix(" - ")
|
||||
?: context.getString(R.string.trailer)
|
||||
RemoteTrailer(name, url)
|
||||
}
|
||||
}.orEmpty()
|
||||
.sortedBy { it.name }
|
||||
val localTrailerCount = item.data.localTrailerCount ?: 0
|
||||
val localTrailers =
|
||||
if (localTrailerCount > 0) {
|
||||
api.userLibraryApi.getLocalTrailers(itemId).content.map {
|
||||
LocalTrailer(BaseItem.Companion.from(it, api))
|
||||
}
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
val trailers = trailerService.getTrailers(item)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@MovieViewModel.trailers.value = localTrailers + remoteTrailers
|
||||
this@MovieViewModel.trailers.value = trailers
|
||||
}
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import coil3.compose.AsyncImage
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
|
|
@ -71,6 +72,7 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
|||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.detail.movie.TrailerRow
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
|
|
@ -79,6 +81,7 @@ import com.github.damontecres.wholphin.ui.roundMinutes
|
|||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.TrailerService
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
|
|
@ -102,6 +105,7 @@ fun SeriesDetails(
|
|||
|
||||
val item by viewModel.item.observeAsState()
|
||||
val seasons by viewModel.seasons.observeAsState(listOf())
|
||||
val trailers by viewModel.trailers.observeAsState(listOf())
|
||||
val people by viewModel.people.observeAsState(listOf())
|
||||
val similar by viewModel.similar.observeAsState(listOf())
|
||||
|
||||
|
|
@ -133,6 +137,7 @@ fun SeriesDetails(
|
|||
preferences = preferences,
|
||||
series = item,
|
||||
seasons = seasons,
|
||||
trailers = trailers,
|
||||
people = people,
|
||||
similar = similar,
|
||||
played = played,
|
||||
|
|
@ -174,6 +179,9 @@ fun SeriesDetails(
|
|||
val favorite = item.data.userData?.isFavorite ?: false
|
||||
viewModel.setFavorite(item.id, !favorite, null)
|
||||
},
|
||||
trailerOnClick = {
|
||||
TrailerService.onClick(context, it, viewModel::navigateTo)
|
||||
},
|
||||
moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigateTo(it) },
|
||||
|
|
@ -244,7 +252,8 @@ fun SeriesDetails(
|
|||
private const val HEADER_ROW = 0
|
||||
private const val SEASONS_ROW = HEADER_ROW + 1
|
||||
private const val PEOPLE_ROW = SEASONS_ROW + 1
|
||||
private const val SIMILAR_ROW = PEOPLE_ROW + 1
|
||||
private const val TRAILER_ROW = PEOPLE_ROW + 1
|
||||
private const val SIMILAR_ROW = TRAILER_ROW + 1
|
||||
|
||||
@Composable
|
||||
fun SeriesDetailsContent(
|
||||
|
|
@ -252,6 +261,7 @@ fun SeriesDetailsContent(
|
|||
series: BaseItem,
|
||||
seasons: List<BaseItem>,
|
||||
similar: List<BaseItem>,
|
||||
trailers: List<Trailer>,
|
||||
people: List<Person>,
|
||||
played: Boolean,
|
||||
favorite: Boolean,
|
||||
|
|
@ -262,6 +272,7 @@ fun SeriesDetailsContent(
|
|||
playOnClick: () -> Unit,
|
||||
watchOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
trailerOnClick: (Trailer) -> Unit,
|
||||
moreActions: MoreDialogActions,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -452,6 +463,21 @@ fun SeriesDetailsContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
if (trailers.isNotEmpty()) {
|
||||
item {
|
||||
TrailerRow(
|
||||
trailers = trailers,
|
||||
onClickTrailer = {
|
||||
position = TRAILER_ROW
|
||||
trailerOnClick.invoke(it)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[TRAILER_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (similar.isNotEmpty()) {
|
||||
item {
|
||||
ItemRow(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.data.ServerRepository
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
|
|
@ -29,6 +30,7 @@ import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.PeopleFavorites
|
||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.util.TrailerService
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -62,12 +64,14 @@ class SeriesViewModel
|
|||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val peopleFavorites: PeopleFavorites,
|
||||
private val trailerService: TrailerService,
|
||||
) : ItemViewModel(api) {
|
||||
private lateinit var seriesId: UUID
|
||||
private lateinit var prefs: UserPreferences
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
val seasons = MutableLiveData<List<BaseItem>>(listOf())
|
||||
val episodes = MutableLiveData<EpisodeList>(EpisodeList.Loading)
|
||||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||
val people = MutableLiveData<List<Person>>(listOf())
|
||||
val similar = MutableLiveData<List<BaseItem>>()
|
||||
|
||||
|
|
@ -111,6 +115,12 @@ class SeriesViewModel
|
|||
episodes.value = episodeInfo
|
||||
loading.value = LoadingState.Success
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val trailers = trailerService.getTrailers(item)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@SeriesViewModel.trailers.value = trailers
|
||||
}
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val people = peopleFavorites.getPeopleFor(item)
|
||||
this@SeriesViewModel.people.setValueOnMain(people)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.net.toUri
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.LocalTrailer
|
||||
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TrailerService
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
suspend fun getTrailers(item: BaseItem): List<Trailer> {
|
||||
val remoteTrailers =
|
||||
item.data.remoteTrailers
|
||||
?.mapNotNull { t ->
|
||||
t.url?.let { url ->
|
||||
val name =
|
||||
t.name
|
||||
// TODO would be nice to clean up the trailer name
|
||||
// ?.replace(item.name ?: "", "")
|
||||
// ?.removePrefix(" - ")
|
||||
?: context.getString(R.string.trailer)
|
||||
RemoteTrailer(name, url)
|
||||
}
|
||||
}.orEmpty()
|
||||
.sortedBy { it.name }
|
||||
val localTrailerCount = item.data.localTrailerCount ?: 0
|
||||
val localTrailers =
|
||||
if (localTrailerCount > 0) {
|
||||
api.userLibraryApi.getLocalTrailers(item.id).content.map {
|
||||
LocalTrailer(BaseItem.Companion.from(it, api))
|
||||
}
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
return localTrailers + remoteTrailers
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Note: This is explicitly <b>not</b> a member function because the injected Context is not an Activity.
|
||||
* We want to start the intent without a new task which requires the Activity context
|
||||
*
|
||||
* This can be provided by LocalContext.current from Compose
|
||||
*/
|
||||
fun onClick(
|
||||
context: Context,
|
||||
trailer: Trailer,
|
||||
navigateTo: (Destination) -> Unit,
|
||||
) {
|
||||
when (trailer) {
|
||||
is LocalTrailer ->
|
||||
navigateTo.invoke(
|
||||
Destination.Playback(
|
||||
itemId = trailer.baseItem.id,
|
||||
item = trailer.baseItem,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
|
||||
is RemoteTrailer -> {
|
||||
val intent = Intent(Intent.ACTION_VIEW, trailer.url.toUri())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue