Fix issue with scrolling to the right episode if there are specials in the season (#1827)
## Description Fixes an edge case where clicking on an episode to go to Series Overview wouldn't scroll to the episode if there is a special (season 0) episodes in the episode list. Dev note: to simplify things, if the entire dataset is cached, then just search with `indexOf`. Often the data is cached because 20 seasons or 100 episodes are fetched initially which covers many shows. ### Related issues Follow up to #1708 ### Testing Emulator and unit tests ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
c6d81dada8
commit
548ec09af3
3 changed files with 123 additions and 37 deletions
|
|
@ -177,10 +177,11 @@ class SeriesViewModel
|
|||
viewModelScope.launchIO {
|
||||
val index =
|
||||
(seasons as? ApiRequestPager<*>)?.let {
|
||||
findIndexByNumberOrId(
|
||||
findIndexByNumberOrIdFast(
|
||||
seasonEpisodeIds.seasonNumber,
|
||||
seasonEpisodeIds.seasonId,
|
||||
it,
|
||||
null,
|
||||
)
|
||||
} ?: 0
|
||||
Timber.v("Got initial season index: $index")
|
||||
|
|
@ -313,6 +314,7 @@ class SeriesViewModel
|
|||
includeItemTypes = listOf(BaseItemKind.SEASON),
|
||||
sortBy = listOf(ItemSortBy.INDEX_NUMBER),
|
||||
sortOrder = listOf(SortOrder.ASCENDING),
|
||||
enableUserData = seriesPageType == SeriesPageType.DETAILS,
|
||||
fields =
|
||||
if (seriesPageType == SeriesPageType.DETAILS) {
|
||||
listOf(
|
||||
|
|
@ -331,16 +333,8 @@ class SeriesViewModel
|
|||
request,
|
||||
GetItemsRequestHandler,
|
||||
viewModelScope,
|
||||
pageSize = 10,
|
||||
pageSize = 20,
|
||||
).init(seasonNum ?: 0)
|
||||
// val seasons =
|
||||
// GetItemsRequestHandler.execute(api, request).content.items.map {
|
||||
// BaseItem.from(
|
||||
// it,
|
||||
// api,
|
||||
// )
|
||||
// }
|
||||
// Timber.v("Loaded ${seasons.size} seasons for series ${series.id}")
|
||||
pager
|
||||
}
|
||||
|
||||
|
|
@ -362,6 +356,7 @@ class SeriesViewModel
|
|||
ItemFields.CUSTOM_RATING,
|
||||
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
|
||||
ItemFields.CAN_DELETE,
|
||||
ItemFields.PARENT_ID,
|
||||
),
|
||||
)
|
||||
Timber.v(
|
||||
|
|
@ -373,10 +368,10 @@ class SeriesViewModel
|
|||
pager.init(episodeNumber ?: 0)
|
||||
val initialIndex =
|
||||
if (episodeId != null || episodeNumber != null) {
|
||||
findIndexByNumberOrId(episodeNumber, episodeId, pager)
|
||||
findIndexByNumberOrIdFast(episodeNumber, episodeId, pager, seasonId)
|
||||
.coerceAtLeast(0)
|
||||
} else {
|
||||
// Force the first page to to be fetched
|
||||
// Force the first page to be fetched
|
||||
if (pager.isNotEmpty()) {
|
||||
pager.getBlocking(0)
|
||||
}
|
||||
|
|
@ -728,6 +723,32 @@ private fun checkNumberOrId(
|
|||
equalsNotNull(indexNumber, targetNum)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index in the [list] where the item's `indexNumber`==[targetNum] or `id`==[targetId]
|
||||
*
|
||||
* If the list is smaller than its page size, then the entire dataset is cached and
|
||||
* [BlockingList.indexOfBlocking] is used, otherwise this function calls [findIndexByNumberOrId]
|
||||
*/
|
||||
suspend fun findIndexByNumberOrIdFast(
|
||||
targetNum: Int?,
|
||||
targetId: UUID?,
|
||||
list: ApiRequestPager<*>,
|
||||
parentId: UUID?,
|
||||
): Int =
|
||||
if (list.size <= list.pageSize) {
|
||||
Timber.v("Using findIndexByNumberOrIdFast indexOfBlocking method")
|
||||
list.indexOfBlocking {
|
||||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id) &&
|
||||
if (parentId != null) {
|
||||
it?.data?.parentId == parentId
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
findIndexByNumberOrId(targetNum, targetId, list as BlockingList<BaseItem?>, parentId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index in the [list] where the item's `indexNumber`==[targetNum] or `id`==[targetId]
|
||||
*
|
||||
|
|
@ -743,7 +764,9 @@ suspend fun findIndexByNumberOrId(
|
|||
targetNum: Int?,
|
||||
targetId: UUID?,
|
||||
list: BlockingList<BaseItem?>,
|
||||
parentId: UUID? = null,
|
||||
): Int {
|
||||
Timber.v("Using findIndexByNumberOrId")
|
||||
// Adjust for 1-based numbers
|
||||
val listIndex = targetNum?.minus(1)?.coerceAtLeast(0)
|
||||
val index =
|
||||
|
|
@ -754,36 +777,55 @@ suspend fun findIndexByNumberOrId(
|
|||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id)
|
||||
}.coerceAtLeast(0)
|
||||
} else if (listIndex != null && listIndex in list.indices) {
|
||||
// Start searching from the target number and choose direction from there
|
||||
val num = list.getBlocking(listIndex)?.indexNumber
|
||||
if (num.lt(targetNum)) {
|
||||
for (i in listIndex + 1 until list.size) {
|
||||
val item = list.getBlocking(i)
|
||||
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
} else if (num.gt(targetNum)) {
|
||||
for (i in listIndex - 1 downTo 0) {
|
||||
val item = list.getBlocking(i)
|
||||
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
list
|
||||
.indexOfBlocking {
|
||||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id)
|
||||
}.coerceAtLeast(0)
|
||||
}
|
||||
searchList(listIndex, targetNum, targetId, list, parentId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private suspend fun searchList(
|
||||
listIndex: Int,
|
||||
targetNum: Int,
|
||||
targetId: UUID?,
|
||||
list: BlockingList<BaseItem?>,
|
||||
parentId: UUID?,
|
||||
): Int {
|
||||
val item = list.getBlocking(listIndex)
|
||||
if (parentId != null && item?.data?.parentId != parentId) {
|
||||
return if (listIndex - 1 in list.indices) {
|
||||
searchList(listIndex - 1, targetNum, targetId, list, parentId)
|
||||
} else if (listIndex + 1 in list.indices) {
|
||||
searchList(listIndex + 1, targetNum, targetId, list, parentId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
val num = item?.indexNumber
|
||||
if (num.lt(targetNum)) {
|
||||
for (i in listIndex + 1 until list.size) {
|
||||
val item = list.getBlocking(i)
|
||||
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
} else if (num.gt(targetNum)) {
|
||||
for (i in listIndex - 1 downTo 0) {
|
||||
val item = list.getBlocking(i)
|
||||
if (checkNumberOrId(targetNum, targetId, item?.indexNumber, item?.id)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
} else {
|
||||
return list
|
||||
.indexOfBlocking {
|
||||
checkNumberOrId(targetNum, targetId, it?.indexNumber, it?.id)
|
||||
}.coerceAtLeast(0)
|
||||
}
|
||||
}
|
||||
|
||||
data class SeriesState(
|
||||
val series: DataLoadingState<BaseItem> = DataLoadingState.Pending,
|
||||
val seasons: List<BaseItem?> = emptyList(),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import java.util.function.Predicate
|
|||
*/
|
||||
abstract class RequestPager<T>(
|
||||
protected val scope: CoroutineScope,
|
||||
protected val pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||
val pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||
cacheSize: Long = 8,
|
||||
) : AbstractList<T?>(),
|
||||
BlockingList<T?> {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import org.junit.Assert
|
|||
import org.junit.Test
|
||||
|
||||
class TestFindIndexByNumberOrId {
|
||||
val season0Id = UUID.randomUUID()
|
||||
val season2Id = UUID.randomUUID()
|
||||
|
||||
fun create(
|
||||
indexNumber: Int,
|
||||
parentIndexNumber: Int,
|
||||
|
|
@ -20,6 +23,12 @@ class TestFindIndexByNumberOrId {
|
|||
type = BaseItemKind.EPISODE,
|
||||
indexNumber = indexNumber,
|
||||
parentIndexNumber = parentIndexNumber,
|
||||
parentId =
|
||||
when (parentIndexNumber) {
|
||||
0 -> season0Id
|
||||
2 -> season2Id
|
||||
else -> null
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -151,4 +160,39 @@ class TestFindIndexByNumberOrId {
|
|||
Assert.assertEquals(2, index)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test with special in middle`() =
|
||||
runTest {
|
||||
val episodes =
|
||||
BlockingList.of<BaseItem?>(
|
||||
listOf(
|
||||
epS02E01,
|
||||
epS02E02,
|
||||
epS02E03,
|
||||
create(6, 0),
|
||||
epS02E04,
|
||||
epS02E05,
|
||||
epS02E06,
|
||||
),
|
||||
)
|
||||
|
||||
findIndexByNumberOrId(
|
||||
targetNum = 3,
|
||||
targetId = epS02E03.id,
|
||||
list = episodes,
|
||||
parentId = season2Id,
|
||||
).let { index ->
|
||||
Assert.assertEquals(2, index)
|
||||
}
|
||||
|
||||
findIndexByNumberOrId(
|
||||
targetNum = 4,
|
||||
targetId = epS02E04.id,
|
||||
list = episodes,
|
||||
parentId = season2Id,
|
||||
).let { index ->
|
||||
Assert.assertEquals(4, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue