mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-07 16:06:35 +01:00
Fixes to season/episode search with duplicates (#1946)
## Description Fixes cases where the server has duplicate season IDs causing the season index search to fail and default to the first season in the list. ### Related issues Fixes #1837 ### Testing Emulator & unit tests ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
1778bdb34c
commit
4a567cbcf4
3 changed files with 108 additions and 35 deletions
|
|
@ -92,7 +92,7 @@ fun SeriesOverview(
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (state.seasons.isNotEmpty()) {
|
if (state.seasons.isNotEmpty()) {
|
||||||
state.seasons.getOrNull(position.seasonTabIndex)?.let {
|
state.seasons.getOrNull(position.seasonTabIndex)?.let {
|
||||||
viewModel.loadEpisodes(it.id)
|
viewModel.loadEpisodes(it.id, it.indexNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +210,7 @@ fun SeriesOverview(
|
||||||
onChangeSeason = { index ->
|
onChangeSeason = { index ->
|
||||||
if (index != position.seasonTabIndex) {
|
if (index != position.seasonTabIndex) {
|
||||||
state.seasons.getOrNull(index)?.let { season ->
|
state.seasons.getOrNull(index)?.let { season ->
|
||||||
viewModel.loadEpisodes(season.id)
|
viewModel.loadEpisodes(season.id, season.indexNumber)
|
||||||
viewModel.position.update {
|
viewModel.position.update {
|
||||||
SeriesOverviewPosition(index, 0)
|
SeriesOverviewPosition(index, 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ class SeriesViewModel
|
||||||
if (seasonEpisodeIds != null) {
|
if (seasonEpisodeIds != null) {
|
||||||
loadEpisodesInternal(
|
loadEpisodesInternal(
|
||||||
seasonEpisodeIds.seasonId,
|
seasonEpisodeIds.seasonId,
|
||||||
|
seasonEpisodeIds.seasonNumber,
|
||||||
seasonEpisodeIds.episodeId,
|
seasonEpisodeIds.episodeId,
|
||||||
seasonEpisodeIds.episodeNumber,
|
seasonEpisodeIds.episodeNumber,
|
||||||
)
|
)
|
||||||
|
|
@ -150,6 +151,7 @@ class SeriesViewModel
|
||||||
seasonsDeferred.await().firstOrNull()?.let {
|
seasonsDeferred.await().firstOrNull()?.let {
|
||||||
loadEpisodesInternal(
|
loadEpisodesInternal(
|
||||||
it.id,
|
it.id,
|
||||||
|
it.indexNumber,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
@ -178,10 +180,11 @@ class SeriesViewModel
|
||||||
val index =
|
val index =
|
||||||
(seasons as? ApiRequestPager<*>)?.let {
|
(seasons as? ApiRequestPager<*>)?.let {
|
||||||
findIndexByNumberOrIdFast(
|
findIndexByNumberOrIdFast(
|
||||||
seasonEpisodeIds.seasonNumber,
|
targetNum = seasonEpisodeIds.seasonNumber,
|
||||||
seasonEpisodeIds.seasonId,
|
targetId = seasonEpisodeIds.seasonId,
|
||||||
it,
|
list = it,
|
||||||
null,
|
parentId = null,
|
||||||
|
parentIndex = null,
|
||||||
)
|
)
|
||||||
} ?: 0
|
} ?: 0
|
||||||
Timber.v("Got initial season index: $index")
|
Timber.v("Got initial season index: $index")
|
||||||
|
|
@ -340,6 +343,7 @@ class SeriesViewModel
|
||||||
|
|
||||||
private suspend fun loadEpisodesInternal(
|
private suspend fun loadEpisodesInternal(
|
||||||
seasonId: UUID,
|
seasonId: UUID,
|
||||||
|
seasonNum: Int?,
|
||||||
episodeId: UUID?,
|
episodeId: UUID?,
|
||||||
episodeNumber: Int?,
|
episodeNumber: Int?,
|
||||||
): EpisodeList {
|
): EpisodeList {
|
||||||
|
|
@ -368,8 +372,13 @@ class SeriesViewModel
|
||||||
pager.init(episodeNumber ?: 0)
|
pager.init(episodeNumber ?: 0)
|
||||||
val initialIndex =
|
val initialIndex =
|
||||||
if (episodeId != null || episodeNumber != null) {
|
if (episodeId != null || episodeNumber != null) {
|
||||||
findIndexByNumberOrIdFast(episodeNumber, episodeId, pager, seasonId)
|
findIndexByNumberOrIdFast(
|
||||||
.coerceAtLeast(0)
|
targetNum = episodeNumber,
|
||||||
|
targetId = episodeId,
|
||||||
|
list = pager,
|
||||||
|
parentId = seasonId,
|
||||||
|
parentIndex = seasonNum,
|
||||||
|
).coerceAtLeast(0)
|
||||||
} else {
|
} else {
|
||||||
// Force the first page to be fetched
|
// Force the first page to be fetched
|
||||||
if (pager.isNotEmpty()) {
|
if (pager.isNotEmpty()) {
|
||||||
|
|
@ -381,7 +390,10 @@ class SeriesViewModel
|
||||||
return EpisodeList.Success(seasonId, pager, initialIndex)
|
return EpisodeList.Success(seasonId, pager, initialIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadEpisodes(seasonId: UUID) {
|
fun loadEpisodes(
|
||||||
|
seasonId: UUID,
|
||||||
|
seasonNum: Int?,
|
||||||
|
) {
|
||||||
val currentEpisodes = (state.value.episodes as? EpisodeList.Success)
|
val currentEpisodes = (state.value.episodes as? EpisodeList.Success)
|
||||||
if (currentEpisodes == null || currentEpisodes.seasonId != seasonId) {
|
if (currentEpisodes == null || currentEpisodes.seasonId != seasonId) {
|
||||||
_state.update {
|
_state.update {
|
||||||
|
|
@ -395,7 +407,7 @@ class SeriesViewModel
|
||||||
viewModelScope.launchIO(ExceptionHandler(true)) {
|
viewModelScope.launchIO(ExceptionHandler(true)) {
|
||||||
val episodes =
|
val episodes =
|
||||||
try {
|
try {
|
||||||
loadEpisodesInternal(seasonId, null, null)
|
loadEpisodesInternal(seasonId, seasonNum, null, null)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Error loading episodes for $seriesId for season $seasonId")
|
Timber.e(e, "Error loading episodes for $seriesId for season $seasonId")
|
||||||
EpisodeList.Error(e)
|
EpisodeList.Error(e)
|
||||||
|
|
@ -716,12 +728,18 @@ private fun checkNumberOrId(
|
||||||
targetId: UUID?,
|
targetId: UUID?,
|
||||||
indexNumber: Int?,
|
indexNumber: Int?,
|
||||||
id: UUID?,
|
id: UUID?,
|
||||||
): Boolean =
|
): Boolean = equalsNotNull(targetId, id) || equalsNotNull(indexNumber, targetNum)
|
||||||
if (targetId != null) {
|
|
||||||
equalsNotNull(targetId, id)
|
private fun checkParent(
|
||||||
} else {
|
parentId: UUID?,
|
||||||
equalsNotNull(indexNumber, targetNum)
|
parentIndex: Int?,
|
||||||
}
|
item: BaseItem?,
|
||||||
|
) = if (parentId != null || parentIndex != null) {
|
||||||
|
equalsNotNull(parentId, item?.data?.parentId) ||
|
||||||
|
equalsNotNull(parentIndex, item?.data?.parentIndexNumber)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index in the [list] where the item's `indexNumber`==[targetNum] or `id`==[targetId]
|
* Find the index in the [list] where the item's `indexNumber`==[targetNum] or `id`==[targetId]
|
||||||
|
|
@ -734,19 +752,22 @@ suspend fun findIndexByNumberOrIdFast(
|
||||||
targetId: UUID?,
|
targetId: UUID?,
|
||||||
list: ApiRequestPager<*>,
|
list: ApiRequestPager<*>,
|
||||||
parentId: UUID?,
|
parentId: UUID?,
|
||||||
|
parentIndex: Int?,
|
||||||
): Int =
|
): Int =
|
||||||
if (list.size <= list.pageSize) {
|
if (list.size <= list.pageSize) {
|
||||||
Timber.v("Using findIndexByNumberOrIdFast indexOfBlocking method")
|
Timber.v("Using findIndexByNumberOrIdFast indexOfBlocking method")
|
||||||
list.indexOfBlocking {
|
list.indexOfBlocking {
|
||||||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id) &&
|
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id) &&
|
||||||
if (parentId != null) {
|
checkParent(parentId, parentIndex, it)
|
||||||
it?.data?.parentId == parentId
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
findIndexByNumberOrId(targetNum, targetId, list as BlockingList<BaseItem?>, parentId)
|
findIndexByNumberOrId(
|
||||||
|
targetNum,
|
||||||
|
targetId,
|
||||||
|
list as BlockingList<BaseItem?>,
|
||||||
|
parentId,
|
||||||
|
parentIndex,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -765,6 +786,7 @@ suspend fun findIndexByNumberOrId(
|
||||||
targetId: UUID?,
|
targetId: UUID?,
|
||||||
list: BlockingList<BaseItem?>,
|
list: BlockingList<BaseItem?>,
|
||||||
parentId: UUID? = null,
|
parentId: UUID? = null,
|
||||||
|
parentIndex: Int? = null,
|
||||||
): Int {
|
): Int {
|
||||||
Timber.v("Using findIndexByNumberOrId")
|
Timber.v("Using findIndexByNumberOrId")
|
||||||
// Adjust for 1-based numbers
|
// Adjust for 1-based numbers
|
||||||
|
|
@ -777,7 +799,7 @@ suspend fun findIndexByNumberOrId(
|
||||||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id)
|
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id)
|
||||||
}.coerceAtLeast(0)
|
}.coerceAtLeast(0)
|
||||||
} else if (listIndex != null && listIndex in list.indices) {
|
} else if (listIndex != null && listIndex in list.indices) {
|
||||||
searchList(listIndex, targetNum, targetId, list, parentId)
|
searchList(listIndex, targetNum, targetId, list, parentId, parentIndex)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
@ -790,22 +812,42 @@ private suspend fun searchList(
|
||||||
targetId: UUID?,
|
targetId: UUID?,
|
||||||
list: BlockingList<BaseItem?>,
|
list: BlockingList<BaseItem?>,
|
||||||
parentId: UUID?,
|
parentId: UUID?,
|
||||||
|
parentIndex: Int?,
|
||||||
): Int {
|
): Int {
|
||||||
val item = list.getBlocking(listIndex)
|
var index = listIndex
|
||||||
if (parentId != null && item?.data?.parentId != parentId) {
|
var item = list.getBlocking(index)
|
||||||
return if (listIndex - 1 in list.indices) {
|
if ((parentId != null || parentIndex != null) && !checkParent(parentId, parentIndex, item)) {
|
||||||
searchList(listIndex - 1, targetNum, targetId, list, parentId)
|
var leftIndex = listIndex - 1
|
||||||
} else if (listIndex + 1 in list.indices) {
|
var rightIndex = listIndex + 1
|
||||||
searchList(listIndex + 1, targetNum, targetId, list, parentId)
|
// Search before & after to find an episode with the right parent
|
||||||
} else {
|
while (leftIndex in list.indices || rightIndex in list.indices) {
|
||||||
0
|
if (leftIndex in list.indices) {
|
||||||
|
val left = list.getBlocking(leftIndex)
|
||||||
|
if (checkParent(parentId, parentIndex, left)) {
|
||||||
|
index = leftIndex
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rightIndex in list.indices) {
|
||||||
|
val right = list.getBlocking(rightIndex)
|
||||||
|
if (checkParent(parentId, parentIndex, right)) {
|
||||||
|
index = rightIndex
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leftIndex--
|
||||||
|
rightIndex++
|
||||||
}
|
}
|
||||||
|
// Search for the right episode starting from this new one
|
||||||
|
item = list.getBlocking(index)
|
||||||
}
|
}
|
||||||
val num = item?.indexNumber
|
val num = item?.indexNumber
|
||||||
if (num.lt(targetNum)) {
|
if (num.lt(targetNum)) {
|
||||||
for (i in listIndex + 1 until list.size) {
|
for (i in listIndex + 1 until list.size) {
|
||||||
val item = list.getBlocking(i)
|
val item = list.getBlocking(i)
|
||||||
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id)) {
|
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id) &&
|
||||||
|
checkParent(parentId, parentIndex, item)
|
||||||
|
) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -813,15 +855,22 @@ private suspend fun searchList(
|
||||||
} else if (num.gt(targetNum)) {
|
} else if (num.gt(targetNum)) {
|
||||||
for (i in listIndex - 1 downTo 0) {
|
for (i in listIndex - 1 downTo 0) {
|
||||||
val item = list.getBlocking(i)
|
val item = list.getBlocking(i)
|
||||||
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id)) {
|
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id) &&
|
||||||
|
checkParent(parentId, parentIndex, item)
|
||||||
|
) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
} else if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id) &&
|
||||||
|
checkParent(parentId, parentIndex, item)
|
||||||
|
) {
|
||||||
|
return index
|
||||||
} else {
|
} else {
|
||||||
return list
|
return list
|
||||||
.indexOfBlocking {
|
.indexOfBlocking {
|
||||||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id)
|
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id) &&
|
||||||
|
checkParent(parentId, parentIndex, item)
|
||||||
}.coerceAtLeast(0)
|
}.coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,12 @@ class TestFindIndexByNumberOrId {
|
||||||
fun `Test with special matching number`() =
|
fun `Test with special matching number`() =
|
||||||
runTest {
|
runTest {
|
||||||
val list = listOf(epS02E01, epS00E02, epS02E02)
|
val list = listOf(epS02E01, epS00E02, epS02E02)
|
||||||
findIndexByNumberOrId(targetNum = 2, targetId = epS02E02.id, list = BlockingList.of(list)).let { index ->
|
findIndexByNumberOrId(
|
||||||
|
targetNum = 2,
|
||||||
|
targetId = epS02E02.id,
|
||||||
|
list = BlockingList.of(list),
|
||||||
|
parentIndex = 2,
|
||||||
|
).let { index ->
|
||||||
Assert.assertEquals(2, index)
|
Assert.assertEquals(2, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,4 +200,23 @@ class TestFindIndexByNumberOrId {
|
||||||
Assert.assertEquals(4, index)
|
Assert.assertEquals(4, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test with non-matching parent ID`() =
|
||||||
|
runTest {
|
||||||
|
val altSeason2Id = UUID.randomUUID()
|
||||||
|
Assert.assertNotEquals(altSeason2Id, season2Id)
|
||||||
|
Assert.assertNotEquals(altSeason2Id, epS02E02.data.parentId)
|
||||||
|
|
||||||
|
val list = listOf(epS02E01, epS02E02)
|
||||||
|
findIndexByNumberOrId(
|
||||||
|
targetNum = 2,
|
||||||
|
targetId = epS02E02.id,
|
||||||
|
list = BlockingList.of(list),
|
||||||
|
parentId = altSeason2Id,
|
||||||
|
parentIndex = 2,
|
||||||
|
).let { index ->
|
||||||
|
Assert.assertEquals(1, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue