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 a274caa1..3144b014 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 @@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.data.model +import androidx.compose.runtime.Stable import com.github.damontecres.wholphin.api.seerr.model.CreditCast import com.github.damontecres.wholphin.api.seerr.model.CreditCrew import com.github.damontecres.wholphin.api.seerr.model.MovieDetails @@ -74,6 +75,7 @@ enum class SeerrAvailability( /** * An item provided by a discovery service (ie Seerr). It may exist on the JF server as well. */ +@Stable @Serializable data class DiscoverItem( val id: Int, diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt index 970e80c3..6db60574 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt @@ -57,6 +57,7 @@ class SeerrServerRepository connection.map { (it as? SeerrConnectionStatus.Success)?.current?.server } val currentUser: Flow = connection.map { (it as? SeerrConnectionStatus.Success)?.current?.user } + val currentUserId: Flow = current.map { it?.config?.id } /** * Whether Seerr integration is currently active of not diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt index e38ba41b..14aa0b9a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrService.kt @@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.services import com.github.damontecres.wholphin.api.seerr.SeerrApiClient import com.github.damontecres.wholphin.api.seerr.model.SearchGet200ResponseResultsInner +import com.github.damontecres.wholphin.api.seerr.model.TvDetails import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.DiscoverItem import kotlinx.coroutines.flow.first @@ -125,4 +126,16 @@ class SeerrService } else { null } + + suspend fun getTvSeries(item: BaseItem): TvDetails? = + if (active.first()) { + item.data.providerIds + ?.get("Tmdb") + ?.toIntOrNull() + ?.let { tvId -> + api.tvApi.tvTvIdGet(tvId = tvId) + } + } else { + null + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt index 3c036534..12acfd37 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt @@ -35,7 +35,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import com.github.damontecres.wholphin.R -import com.github.damontecres.wholphin.api.seerr.model.Season import com.github.damontecres.wholphin.api.seerr.model.TvDetails import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.DiscoverItem @@ -99,6 +98,7 @@ fun DiscoverSeriesDetails( var overviewDialog by remember { mutableStateOf(null) } var seasonDialog by remember { mutableStateOf(null) } var moreDialog by remember { mutableStateOf(null) } + var showRequestSeasonDialog by remember { mutableStateOf(false) } val requestStr = stringResource(R.string.request) val request4kStr = stringResource(R.string.request_4k) @@ -159,26 +159,7 @@ fun DiscoverSeriesDetails( trailers = trailers, requestOnClick = { item.id?.let { id -> - if (request4kEnabled) { - moreDialog = - DialogParams( - fromLongClick = false, - title = item.name ?: "", - items = - listOf( - DialogItem( - text = requestStr, - onClick = { viewModel.request(id, false) }, - ), - DialogItem( - text = request4kStr, - onClick = { viewModel.request(id, true) }, - ), - ), - ) - } else { - viewModel.request(id, false) - } + showRequestSeasonDialog = true } }, cancelOnClick = { @@ -218,6 +199,18 @@ fun DiscoverSeriesDetails( waitToLoad = params.fromLongClick, ) } + if (showRequestSeasonDialog) { + RequestSeasonsDialog( + title = item?.name ?: "", + seasons = seasons, + request4kEnabled = request4kEnabled, + onSubmit = { seasons, is4k -> + item?.id?.let { viewModel.request(it, seasons, is4k) } + showRequestSeasonDialog = false + }, + onDismissRequest = { showRequestSeasonDialog = false }, + ) + } } private const val HEADER_ROW = 0 @@ -234,7 +227,7 @@ fun DiscoverSeriesDetailsContent( series: TvDetails, rating: DiscoverRating?, canCancel: Boolean, - seasons: List, + seasons: List, similar: List, recommended: List, trailers: List, @@ -299,6 +292,7 @@ fun DiscoverSeriesDetailsContent( SeerrAvailability.from(series.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, requestOnClick = requestOnClick, + pendingOnClick = requestOnClick, cancelOnClick = cancelOnClick, moreOnClick = moreOnClick, goToOnClick = goToOnClick, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt index f7b11137..da6550ec 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt @@ -6,12 +6,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest -import com.github.damontecres.wholphin.api.seerr.model.Season +import com.github.damontecres.wholphin.api.seerr.model.RequestRequestIdPutRequest import com.github.damontecres.wholphin.api.seerr.model.TvDetails import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverRating import com.github.damontecres.wholphin.data.model.RemoteTrailer +import com.github.damontecres.wholphin.data.model.SeerrAvailability import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.NavigationManager @@ -33,6 +34,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update @@ -63,7 +65,7 @@ class DiscoverSeriesViewModel val tvSeries = MutableLiveData(null) val rating = MutableLiveData(null) - val seasons = MutableLiveData>(listOf()) + val seasons = MutableLiveData>(listOf()) val trailers = MutableLiveData>(listOf()) val people = MutableLiveData>(listOf()) val similar = MutableLiveData>() @@ -103,6 +105,7 @@ class DiscoverSeriesViewModel val discoveredItem = DiscoverItem(tv) backdropService.submit(discoveredItem) + updateSeasonStatus() updateCanCancel() withContext(Dispatchers.Main) { @@ -157,6 +160,43 @@ class DiscoverSeriesViewModel navigationManager.navigateTo(destination) } + private suspend fun updateSeasonStatus() { + tvSeries.value?.let { tv -> + val seasonStatus = mutableMapOf() + tv.seasons?.forEach { + it.seasonNumber?.let { + seasonStatus[it] = SeerrAvailability.UNKNOWN + } + } + val tvStatus = + SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN + tv.mediaInfo + ?.requests + ?.forEach { + it.seasons?.mapNotNull { season -> + season.seasonNumber?.let { + val current = seasonStatus[season.seasonNumber] + val new = + SeerrAvailability + .from(season.status) + ?.takeIf { it != SeerrAvailability.UNKNOWN } ?: tvStatus + if (current == null || new.status > current.status) { + seasonStatus[season.seasonNumber] = new + } + } + } + } + Timber.v("seasonStatus=%s", seasonStatus) + val requestSeasons = + seasonStatus.mapNotNull { (seasonNumber, availability) -> + tv.seasons?.firstOrNull { it.seasonNumber == seasonNumber }?.let { + RequestSeason(it, availability) + } + } + seasons.setValueOnMain(requestSeasons) + } + } + private suspend fun updateCanCancel() { val user = userConfig.firstOrNull() val canCancel = canUserCancelRequest(user, tvSeries.value?.mediaInfo?.requests) @@ -165,20 +205,43 @@ class DiscoverSeriesViewModel fun request( id: Int, + seasons: Set, is4k: Boolean, ) { viewModelScope.launchIO { - val request = - seerrService.api.requestApi.requestPost( - RequestPostRequest( - is4k = is4k, - mediaId = id, - mediaType = RequestPostRequest.MediaType.TV, - seasons = RequestPostRequest.Seasons.ALL, // TODO handle picking seasons - ), - ) - fetchAndSetItem().await() - updateCanCancel() + tvSeries.value?.let { tv -> + val currentRequest = + tv.mediaInfo?.requests?.firstOrNull { + it.requestedBy?.id == + seerrServerRepository.currentUserId.first() + } + if (currentRequest != null) { + Timber.v("User has pending request, will update") + seerrService.api.requestApi.requestRequestIdPut( + requestId = currentRequest.id.toString(), + requestRequestIdPutRequest = + RequestRequestIdPutRequest( + is4k = is4k, + mediaType = RequestRequestIdPutRequest.MediaType.TV, + seasons = seasons.toList(), + ), + ) + } else { + Timber.v("New request for %s seasons", seasons.size) + seerrService.api.requestApi.requestPost( + RequestPostRequest( + is4k = is4k, + mediaId = id, + mediaType = RequestPostRequest.MediaType.TV, + seasons = seasons.toList(), + ), + ) + } + + fetchAndSetItem().await() + updateSeasonStatus() + updateCanCancel() + } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/ExpandableDiscoverButtons.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/ExpandableDiscoverButtons.kt index 4b73a0b2..2c3fc036 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/ExpandableDiscoverButtons.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/ExpandableDiscoverButtons.kt @@ -37,6 +37,7 @@ fun ExpandableDiscoverButtons( trailerOnClick: (Trailer) -> Unit, buttonOnFocusChanged: (FocusState) -> Unit, modifier: Modifier = Modifier, + pendingOnClick: () -> Unit = {}, ) { val firstFocus = remember { FocusRequester() } LazyRow( @@ -89,7 +90,7 @@ fun ExpandableDiscoverButtons( SeerrAvailability.PENDING, SeerrAvailability.PROCESSING, -> { - // TODO? + pendingOnClick.invoke() } SeerrAvailability.PARTIALLY_AVAILABLE, @@ -109,6 +110,21 @@ fun ExpandableDiscoverButtons( .onFocusChanged(buttonOnFocusChanged), ) } + if (availability == SeerrAvailability.PARTIALLY_AVAILABLE) { + item("request_partial") { + ExpandableFaButton( + title = R.string.request, + iconStringRes = R.string.fa_download, + onClick = { + requestOnClick.invoke() + }, + enabled = availability == SeerrAvailability.PARTIALLY_AVAILABLE, + modifier = + Modifier + .onFocusChanged(buttonOnFocusChanged), + ) + } + } if (canCancel) { item("cancel") { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/RequestSeasons.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/RequestSeasons.kt new file mode 100644 index 00000000..4a56cb88 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/RequestSeasons.kt @@ -0,0 +1,323 @@ +package com.github.damontecres.wholphin.ui.detail.discover + +import android.content.res.Configuration.UI_MODE_TYPE_TELEVISION +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableStateSetOf +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.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.tv.material3.Button +import androidx.tv.material3.ClickableSurfaceDefaults +import androidx.tv.material3.ListItem +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Surface +import androidx.tv.material3.Switch +import androidx.tv.material3.Text +import androidx.tv.material3.contentColorFor +import com.github.damontecres.wholphin.R +import com.github.damontecres.wholphin.api.seerr.model.Season +import com.github.damontecres.wholphin.data.model.SeerrAvailability +import com.github.damontecres.wholphin.ui.cards.AvailableIndicator +import com.github.damontecres.wholphin.ui.cards.PartiallyAvailableIndicator +import com.github.damontecres.wholphin.ui.cards.PendingIndicator +import com.github.damontecres.wholphin.ui.components.BasicDialog +import com.github.damontecres.wholphin.ui.theme.WholphinTheme + +data class RequestSeason( + val season: Season, + val availability: SeerrAvailability, +) + +@Composable +fun RequestSeasons( + title: String, + seasons: List, + onSubmit: (Set, Boolean) -> Unit, + request4kEnabled: Boolean, + modifier: Modifier, +) { + val allSeasonNumbers = remember(seasons) { seasons.mapNotNull { it.season.seasonNumber }.toSet() } + val selected = + remember { + mutableStateSetOf( + *seasons + .mapNotNull { + if (it.availability > SeerrAvailability.UNKNOWN) { + it.season.seasonNumber + } else { + null + } + }.toTypedArray(), + ) + } + var is4k by remember { mutableStateOf(false) } + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = modifier, + ) { + Text( + text = title, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + LazyColumn { + item { + val isSelected = selected.containsAll(allSeasonNumbers) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth(), + ) { + ClickSwitch( + label = stringResource(R.string.select_all), + checked = isSelected, + onClick = { + if (isSelected) { + selected.removeAll(allSeasonNumbers) + } else { + selected.addAll(allSeasonNumbers) + } + }, + ) + Button( + onClick = { onSubmit.invoke(selected, is4k) }, + ) { + Text( + text = stringResource(R.string.submit), + ) + } + } + } + if (request4kEnabled) { + item { + ClickSwitch( + label = stringResource(R.string.request_4k), + checked = is4k, + onClick = { is4k = !is4k }, + ) + } + } + itemsIndexed(seasons) { index, season -> + val seasonNumber = season.season.seasonNumber + val isSelected = seasonNumber in selected + SeasonListItem( + season = season, + selected = isSelected, + onClick = { + if (isSelected) { + selected.remove(seasonNumber) + } else { + seasonNumber?.let { selected.add(it) } + } + }, + modifier = Modifier, + ) + } + if (seasons.size > 7) { + item { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + ) { + Button( + onClick = { onSubmit.invoke(selected, is4k) }, + ) { + Text( + text = stringResource(R.string.submit), + ) + } + } + } + } + } + } +} + +@Composable +fun SeasonListItem( + season: RequestSeason, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + selected = false, + headlineContent = { + Text( + text = + season.season.name + ?: (stringResource(R.string.tv_season) + " ${season.season.seasonNumber}"), + ) + }, + supportingContent = { + season.season.episodeCount?.let { + Text( + // TODO should use plurals string + text = "${season.season.episodeCount} " + stringResource(R.string.episodes), + ) + } + }, + leadingContent = { + when (season.availability) { + SeerrAvailability.UNKNOWN -> {} + + SeerrAvailability.DELETED -> {} + + SeerrAvailability.PENDING, + SeerrAvailability.PROCESSING, + -> { + PendingIndicator() + } + + SeerrAvailability.PARTIALLY_AVAILABLE -> { + PartiallyAvailableIndicator() + } + + SeerrAvailability.AVAILABLE -> { + AvailableIndicator() + } + } + }, + trailingContent = { + Row { + Switch( + checked = selected, + onCheckedChange = { + onClick.invoke() + }, + ) + } + }, + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun ClickSurface( + onClick: () -> Unit, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit, +) { + Surface( + colors = + ClickableSurfaceDefaults.colors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurface, + focusedContainerColor = MaterialTheme.colorScheme.inverseSurface, + focusedContentColor = contentColorFor(MaterialTheme.colorScheme.inverseSurface), + pressedContainerColor = MaterialTheme.colorScheme.inverseSurface, + pressedContentColor = contentColorFor(MaterialTheme.colorScheme.inverseSurface), + ), + onClick = onClick, + content = content, + modifier = modifier, + ) +} + +@Composable +private fun ClickSwitch( + label: String, + checked: Boolean, + onClick: () -> Unit, +) { + ClickSurface( + onClick = onClick, + modifier = Modifier, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .padding(horizontal = 8.dp) + .height(54.dp), + ) { + Switch( + checked = checked, + onCheckedChange = {}, + modifier = Modifier.padding(end = 8.dp), + ) + Text( + text = label, + ) + } + } +} + +@Composable +fun RequestSeasonsDialog( + title: String, + seasons: List, + request4kEnabled: Boolean, + onSubmit: (Set, Boolean) -> Unit, + onDismissRequest: () -> Unit, +) { + BasicDialog( + onDismissRequest = onDismissRequest, + ) { + RequestSeasons( + title = title, + seasons = seasons, + request4kEnabled = request4kEnabled, + onSubmit = onSubmit, + modifier = Modifier.padding(16.dp), + ) + } +} + +@Preview( + device = "spec:parent=tv_1080p", + backgroundColor = 0xFF383535, + uiMode = UI_MODE_TYPE_TELEVISION, + heightDp = 800, +) +@Composable +fun RequestSeasonsPreview() { + val seasons = + List(10) { + RequestSeason( + season = + Season( + seasonNumber = it + 1, + episodeCount = 10 + it, + ), + availability = + if (it < 3) { + SeerrAvailability.AVAILABLE + } else { + SeerrAvailability.UNKNOWN + }, + ) + } + + WholphinTheme { + RequestSeasons( + title = "Series title", + seasons = seasons, + request4kEnabled = true, + onSubmit = { _, _ -> }, + modifier = Modifier.width(400.dp), + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt index 81beb82f..4f8e4366 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesDetails.kt @@ -1,6 +1,9 @@ package com.github.damontecres.wholphin.ui.detail.series import android.content.Context +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.focusGroup import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -120,6 +123,7 @@ fun SeriesDetails( val people by viewModel.people.observeAsState(listOf()) val similar by viewModel.similar.observeAsState(listOf()) val discovered by viewModel.discovered.collectAsState() + val discoverSeries by viewModel.discoverSeries.collectAsState() val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending) var overviewDialog by remember { mutableStateOf(null) } @@ -230,6 +234,12 @@ fun SeriesDetails( onClickExtra = { _, extra -> viewModel.navigateTo(extra.destination) }, + discoverSeries = discoverSeries, + onClickDiscoverSeries = { + discoverSeries?.let { + viewModel.navigateTo(Destination.DiscoveredItem(it)) + } + }, discovered = discovered, onClickDiscover = { index, item -> viewModel.navigateTo(item.destination) @@ -350,6 +360,8 @@ fun SeriesDetailsContent( onClickExtra: (Int, ExtrasItem) -> Unit, moreActions: MoreDialogActions, onClickDiscover: (Int, DiscoverItem) -> Unit, + discoverSeries: DiscoverItem?, + onClickDiscoverSeries: () -> Unit, modifier: Modifier = Modifier, ) { val context = LocalContext.current @@ -487,6 +499,25 @@ fun SeriesDetailsContent( }, ) } + AnimatedVisibility( + visible = discoverSeries != null, + enter = fadeIn(), + exit = fadeOut(), + ) { + ExpandableFaButton( + title = R.string.discover, + iconStringRes = R.string.fa_magnifying_glass_plus, + onClick = onClickDiscoverSeries, + modifier = + Modifier.onFocusChanged { + if (it.isFocused) { + scope.launch(ExceptionHandler()) { + bringIntoViewRequester.bringIntoView() + } + } + }, + ) + } } } item { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt index 53924431..ee07a170 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/series/SeriesViewModel.kt @@ -58,6 +58,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update @@ -123,6 +124,7 @@ class SeriesViewModel val peopleInEpisode = MutableLiveData(PeopleInItem()) val discovered = MutableStateFlow>(listOf()) + val discoverSeries = MutableStateFlow(null) val position = MutableStateFlow(SeriesOverviewPosition(0, 0)) @@ -232,6 +234,22 @@ class SeriesViewModel val results = seerrService.similar(item).orEmpty() discovered.update { results } } + viewModelScope.launchIO { + seerrService.active.collectLatest { active -> + val tv = + if (active) { + try { + seerrService.getTvSeries(item)?.let { DiscoverItem(it) } + } catch (ex: Exception) { + Timber.e(ex) + null + } + } else { + null + } + discoverSeries.update { tv } + } + } } mediaManagementService.deletedItemFlow .onEach { deletedItem -> diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt index 0f62f12f..02e97ff5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt @@ -98,6 +98,7 @@ class SwitchSeerrViewModel ) } } + serverConnectionStatus.update { LoadingState.Success } } else { val message = results diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7a2fb92..66276854 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -719,5 +719,6 @@ Creator Artist Search %s + Select all diff --git a/app/src/main/seerr/seerr-api.yml b/app/src/main/seerr/seerr-api.yml index d619d042..d6b719ec 100644 --- a/app/src/main/seerr/seerr-api.yml +++ b/app/src/main/seerr/seerr-api.yml @@ -1086,6 +1086,11 @@ components: type: array items: $ref: '#/components/schemas/Episode' + status: + type: integer + example: 0 + description: Status of the request. 1 = PENDING APPROVAL, 2 = APPROVED, 3 = DECLINED + readOnly: true TvDetails: type: object properties: @@ -1268,6 +1273,10 @@ components: type: string type: type: string + seasons: + type: array + items: + $ref: '#/components/schemas/Season' required: - id - status @@ -6135,16 +6144,10 @@ paths: type: integer example: 123 seasons: - # oneOf: - # - type: array - # items: - # type: integer - # minimum: 0 - # - type: string - # enum: [all] - # TODO - type: string - enum: [all] + type: array + items: + type: integer + minimum: 0 nullable: true is4k: type: boolean