mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-05 18:30:59 +01:00
Fix requesting new seasons after approval (#1816)
## Description Fixes requesting additional seasons of a show if a previous request has already been approved Now if the previous request is already approved/completed/etc, a new request will be created instead of trying to update the previous one. Pending requests will still be updated as before instead of requesting a new one. ### Related issues Fixes #1702 ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
548ec09af3
commit
e6304f79a2
5 changed files with 161 additions and 41 deletions
|
|
@ -71,6 +71,23 @@ enum class SeerrAvailability(
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class RequestStatus(
|
||||
val status: Int,
|
||||
) {
|
||||
UNKNOWN(0),
|
||||
PENDING(1),
|
||||
APPROVED(2),
|
||||
DECLINED(3),
|
||||
FAILURE(4),
|
||||
COMPLETED(5),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun from(status: Int?) = RequestStatus.entries.firstOrNull { it.status == status } ?: UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An item provided by a discovery service (ie Seerr). It may exist on the JF server as well, see [availability].
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ fun DiscoverSeriesDetails(
|
|||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val state by viewModel.state.collectAsState()
|
||||
val request4kEnabled by viewModel.request4kEnabled.collectAsState(false)
|
||||
val request4kEnabled by viewModel.request4kEnabled.collectAsState()
|
||||
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var seasonDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
|
|
@ -192,6 +192,7 @@ fun DiscoverSeriesDetails(
|
|||
id = state.tvSeries.successValue?.id ?: -1,
|
||||
title = state.tvSeries.successValue?.name ?: "",
|
||||
seasons = state.seasons,
|
||||
seasons4k = state.seasons4k,
|
||||
request4kEnabled = request4kEnabled,
|
||||
onSubmit = {
|
||||
showRequestSeasonDialog = false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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.RequestStatus
|
||||
import com.github.damontecres.wholphin.data.model.SeerrAvailability
|
||||
import com.github.damontecres.wholphin.data.model.SeerrItemType
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
|
|
@ -36,10 +37,12 @@ import kotlinx.coroutines.Deferred
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import timber.log.Timber
|
||||
|
|
@ -66,7 +69,10 @@ class DiscoverSeriesViewModel
|
|||
val state: StateFlow<DiscoverSeriesState> = _state
|
||||
|
||||
val userConfig = seerrServerRepository.current.map { it?.config }
|
||||
val request4kEnabled = seerrServerRepository.current.map { it?.request4kTvEnabled ?: false }
|
||||
val request4kEnabled =
|
||||
seerrServerRepository.current
|
||||
.map { it?.request4kTvEnabled ?: false }
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||
|
||||
init {
|
||||
init()
|
||||
|
|
@ -153,39 +159,99 @@ class DiscoverSeriesViewModel
|
|||
navigationManager.navigateTo(destination)
|
||||
}
|
||||
|
||||
private fun updateSeasonStatus(tv: TvDetails) {
|
||||
val seasonStatus = mutableMapOf<Int, SeerrAvailability>()
|
||||
private suspend fun updateSeasonStatus(tv: TvDetails) {
|
||||
if (request4kEnabled.first()) {
|
||||
updateSeasonStatus(tv, true)
|
||||
}
|
||||
updateSeasonStatus(tv, false)
|
||||
}
|
||||
|
||||
private suspend fun updateSeasonStatus(
|
||||
tv: TvDetails,
|
||||
is4k: Boolean,
|
||||
) {
|
||||
val currentUserId = seerrServerRepository.currentUserId.first()
|
||||
val seasonStatus = mutableMapOf<Int, RequestStatus>()
|
||||
val seasonAvailability = mutableMapOf<Int, SeerrAvailability>()
|
||||
val editable = mutableMapOf<Int, Boolean>()
|
||||
tv.seasons?.forEach {
|
||||
it.seasonNumber?.let {
|
||||
seasonStatus[it] = SeerrAvailability.UNKNOWN
|
||||
it.seasonNumber?.let { seasonNumber ->
|
||||
seasonStatus[seasonNumber] = RequestStatus.UNKNOWN
|
||||
val status = if (is4k) it.status4k else it.status
|
||||
val availability =
|
||||
SeerrAvailability.from(status) ?: SeerrAvailability.UNKNOWN
|
||||
seasonAvailability[seasonNumber] = availability
|
||||
}
|
||||
}
|
||||
val tvStatus =
|
||||
SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN
|
||||
|
||||
tv.mediaInfo
|
||||
?.requests
|
||||
?.forEach {
|
||||
it.seasons?.mapNotNull { season ->
|
||||
?.filter { it.is4k == is4k }
|
||||
?.forEach { req ->
|
||||
req.seasons?.mapNotNull { season ->
|
||||
season.seasonNumber?.let {
|
||||
val current = seasonStatus[season.seasonNumber]
|
||||
val new =
|
||||
SeerrAvailability
|
||||
.from(season.status)
|
||||
?.takeIf { it != SeerrAvailability.UNKNOWN } ?: tvStatus
|
||||
// Not status4k because the request itself is marked as is4k or not
|
||||
val status = season.status
|
||||
val new = RequestStatus.from(status)
|
||||
if (current == null || new.status > current.status) {
|
||||
seasonStatus[season.seasonNumber] = new
|
||||
}
|
||||
editable[season.seasonNumber] =
|
||||
currentUserId == req.requestedBy?.id && req.status == RequestStatus.PENDING.status
|
||||
}
|
||||
}
|
||||
}
|
||||
Timber.v("seasonAvailability=%s", seasonAvailability)
|
||||
Timber.v("seasonStatus=%s", seasonStatus)
|
||||
val requestSeasons =
|
||||
seasonStatus.mapNotNull { (seasonNumber, availability) ->
|
||||
seasonStatus.mapNotNull { (seasonNumber, status) ->
|
||||
tv.seasons?.firstOrNull { it.seasonNumber == seasonNumber }?.let {
|
||||
RequestSeason(it, availability)
|
||||
val availability =
|
||||
when (status) {
|
||||
RequestStatus.UNKNOWN -> {
|
||||
SeerrAvailability.UNKNOWN
|
||||
}
|
||||
|
||||
RequestStatus.PENDING -> {
|
||||
SeerrAvailability.PENDING
|
||||
}
|
||||
|
||||
RequestStatus.APPROVED -> {
|
||||
SeerrAvailability.PROCESSING
|
||||
}
|
||||
|
||||
RequestStatus.DECLINED -> {
|
||||
SeerrAvailability.UNKNOWN
|
||||
}
|
||||
|
||||
RequestStatus.FAILURE -> {
|
||||
SeerrAvailability.UNKNOWN
|
||||
}
|
||||
|
||||
RequestStatus.COMPLETED -> {
|
||||
seasonAvailability.getOrDefault(
|
||||
seasonNumber,
|
||||
SeerrAvailability.UNKNOWN,
|
||||
)
|
||||
}
|
||||
}
|
||||
RequestSeason(
|
||||
season = it,
|
||||
status = status,
|
||||
availability = availability,
|
||||
editable = editable.getOrDefault(seasonNumber, true),
|
||||
)
|
||||
}
|
||||
}
|
||||
_state.update { it.copy(seasons = requestSeasons) }
|
||||
Timber.v("Got %s seasons, is4k=%s", requestSeasons.size, is4k)
|
||||
_state.update {
|
||||
if (is4k) {
|
||||
it.copy(seasons4k = requestSeasons)
|
||||
} else {
|
||||
it.copy(seasons = requestSeasons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateCanCancel() {
|
||||
|
|
@ -225,10 +291,11 @@ class DiscoverSeriesViewModel
|
|||
fun request(request: TvRequest) {
|
||||
viewModelScope.launchIO {
|
||||
state.value.tvSeries.successValue?.let { tv ->
|
||||
val currentUserId = seerrServerRepository.currentUserId.first()
|
||||
val currentRequest =
|
||||
tv.mediaInfo?.requests?.firstOrNull {
|
||||
it.requestedBy?.id ==
|
||||
seerrServerRepository.currentUserId.first()
|
||||
it.status == RequestStatus.PENDING.status &&
|
||||
it.requestedBy?.id == currentUserId
|
||||
}
|
||||
try {
|
||||
if (currentRequest != null) {
|
||||
|
|
@ -311,6 +378,7 @@ data class DiscoverSeriesState(
|
|||
val tvSeries: DataLoadingState<TvDetails> = DataLoadingState.Pending,
|
||||
val rating: DiscoverRating? = null,
|
||||
val seasons: List<RequestSeason> = emptyList(),
|
||||
val seasons4k: List<RequestSeason> = emptyList(),
|
||||
val trailers: List<Trailer> = emptyList(),
|
||||
val people: List<DiscoverItem> = emptyList(),
|
||||
val similar: List<DiscoverItem> = emptyList(),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ 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.RequestStatus
|
||||
import com.github.damontecres.wholphin.data.model.SeerrAvailability
|
||||
import com.github.damontecres.wholphin.ui.cards.AvailableIndicator
|
||||
import com.github.damontecres.wholphin.ui.cards.PartiallyAvailableIndicator
|
||||
|
|
@ -51,7 +52,9 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
|
||||
data class RequestSeason(
|
||||
val season: Season,
|
||||
val status: RequestStatus,
|
||||
val availability: SeerrAvailability,
|
||||
val editable: Boolean,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -59,26 +62,45 @@ fun RequestSeasons(
|
|||
id: Int,
|
||||
title: String,
|
||||
seasons: List<RequestSeason>,
|
||||
seasons4k: List<RequestSeason>,
|
||||
data: SeerrRequestData,
|
||||
request4kEnabled: Boolean,
|
||||
onSubmit: (TvRequest) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val allSeasonNumbers = remember(seasons) { seasons.mapNotNull { it.season.seasonNumber }.toSet() }
|
||||
val selectedSeasons =
|
||||
remember {
|
||||
mutableStateSetOf<Int>(
|
||||
var is4k by remember { mutableStateOf(request4kEnabled) }
|
||||
val seasons = remember(is4k, seasons, seasons4k) { if (is4k) seasons4k else seasons }
|
||||
|
||||
val allSeasonNumbers =
|
||||
remember(seasons) {
|
||||
seasons
|
||||
.filter { it.editable }
|
||||
.mapNotNull { it.season.seasonNumber }
|
||||
.toSet()
|
||||
}
|
||||
val availableSeasons =
|
||||
remember(seasons) {
|
||||
mutableStateSetOf(
|
||||
*seasons
|
||||
.mapNotNull {
|
||||
if (it.availability > SeerrAvailability.UNKNOWN) {
|
||||
it.season.seasonNumber
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toTypedArray(),
|
||||
.filter { season ->
|
||||
season.status == RequestStatus.PENDING ||
|
||||
season.status == RequestStatus.APPROVED ||
|
||||
season.status == RequestStatus.COMPLETED ||
|
||||
season.availability == SeerrAvailability.PARTIALLY_AVAILABLE ||
|
||||
season.availability == SeerrAvailability.AVAILABLE
|
||||
}.mapNotNull { season -> season.season.seasonNumber }
|
||||
.toTypedArray(),
|
||||
)
|
||||
}
|
||||
val selectedSeasons =
|
||||
remember(seasons) {
|
||||
mutableStateSetOf<Int>(
|
||||
*seasons
|
||||
.filter { season -> season.status == RequestStatus.PENDING }
|
||||
.mapNotNull { season -> season.season.seasonNumber }
|
||||
.toTypedArray(),
|
||||
)
|
||||
}
|
||||
var is4k by remember { mutableStateOf(request4kEnabled) }
|
||||
|
||||
var profile by remember(is4k) {
|
||||
mutableStateOf(
|
||||
|
|
@ -201,12 +223,12 @@ fun RequestSeasons(
|
|||
}
|
||||
itemsIndexed(seasons) { index, season ->
|
||||
val seasonNumber = season.season.seasonNumber
|
||||
val isSelected = seasonNumber in selectedSeasons
|
||||
val checked = seasonNumber in selectedSeasons || seasonNumber in availableSeasons
|
||||
SeasonListItem(
|
||||
season = season,
|
||||
selected = isSelected,
|
||||
checked = checked,
|
||||
onClick = {
|
||||
if (isSelected) {
|
||||
if (checked) {
|
||||
selectedSeasons.remove(seasonNumber)
|
||||
} else {
|
||||
seasonNumber?.let { selectedSeasons.add(it) }
|
||||
|
|
@ -242,11 +264,12 @@ fun RequestSeasons(
|
|||
@Composable
|
||||
fun SeasonListItem(
|
||||
season: RequestSeason,
|
||||
selected: Boolean,
|
||||
checked: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ListItem(
|
||||
enabled = season.editable,
|
||||
selected = false,
|
||||
headlineContent = {
|
||||
Text(
|
||||
|
|
@ -265,9 +288,10 @@ fun SeasonListItem(
|
|||
},
|
||||
leadingContent = {
|
||||
when (season.availability) {
|
||||
SeerrAvailability.UNKNOWN -> {}
|
||||
|
||||
SeerrAvailability.DELETED -> {}
|
||||
SeerrAvailability.UNKNOWN,
|
||||
SeerrAvailability.DELETED,
|
||||
-> {
|
||||
}
|
||||
|
||||
SeerrAvailability.PENDING,
|
||||
SeerrAvailability.PROCESSING,
|
||||
|
|
@ -285,14 +309,14 @@ fun SeasonListItem(
|
|||
|
||||
SeerrAvailability.BLOCKLISTED -> {
|
||||
// TODO handle block listed
|
||||
// BlocklistedIndicator()
|
||||
// BlocklistedIndicator()
|
||||
}
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
Row {
|
||||
Switch(
|
||||
checked = selected,
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
onClick.invoke()
|
||||
},
|
||||
|
|
@ -362,6 +386,7 @@ fun RequestSeasonsDialog(
|
|||
loading: LoadingState,
|
||||
data: SeerrRequestData,
|
||||
seasons: List<RequestSeason>,
|
||||
seasons4k: List<RequestSeason>,
|
||||
request4kEnabled: Boolean,
|
||||
onSubmit: (TvRequest) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
|
|
@ -388,6 +413,7 @@ fun RequestSeasonsDialog(
|
|||
title = title,
|
||||
data = data,
|
||||
seasons = seasons,
|
||||
seasons4k = seasons4k,
|
||||
request4kEnabled = request4kEnabled,
|
||||
onSubmit = onSubmit,
|
||||
modifier =
|
||||
|
|
@ -416,12 +442,14 @@ fun RequestSeasonsPreview() {
|
|||
seasonNumber = it + 1,
|
||||
episodeCount = 10 + it,
|
||||
),
|
||||
status = RequestStatus.UNKNOWN,
|
||||
availability =
|
||||
if (it < 3) {
|
||||
SeerrAvailability.AVAILABLE
|
||||
} else {
|
||||
SeerrAvailability.UNKNOWN
|
||||
},
|
||||
editable = it >= 3,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -430,6 +458,7 @@ fun RequestSeasonsPreview() {
|
|||
id = 1,
|
||||
title = "Series title",
|
||||
seasons = seasons,
|
||||
seasons4k = emptyList(),
|
||||
data =
|
||||
SeerrRequestData(
|
||||
profiles4k =
|
||||
|
|
|
|||
|
|
@ -1090,7 +1090,12 @@ components:
|
|||
status:
|
||||
type: integer
|
||||
example: 0
|
||||
description: Status of the request. 1 = PENDING APPROVAL, 2 = APPROVED, 3 = DECLINED
|
||||
description: Availability of the season
|
||||
readOnly: true
|
||||
status4k:
|
||||
type: integer
|
||||
example: 0
|
||||
description: Availability of the season in 4K
|
||||
readOnly: true
|
||||
TvShowStatus:
|
||||
type: string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue