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 05f82a83..eda99f49 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 @@ -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, 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, + 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, + 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 = DataLoadingState.Pending, val seasons: List = emptyList(), diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/RequestPager.kt b/app/src/main/java/com/github/damontecres/wholphin/util/RequestPager.kt index d8a34de6..10f0f64f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/RequestPager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/RequestPager.kt @@ -24,7 +24,7 @@ import java.util.function.Predicate */ abstract class RequestPager( protected val scope: CoroutineScope, - protected val pageSize: Int = DEFAULT_PAGE_SIZE, + val pageSize: Int = DEFAULT_PAGE_SIZE, cacheSize: Long = 8, ) : AbstractList(), BlockingList { diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestFindIndexByNumberOrId.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestFindIndexByNumberOrId.kt index 6fe8bd3a..6e840dd8 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestFindIndexByNumberOrId.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestFindIndexByNumberOrId.kt @@ -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( + 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) + } + } }