mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Use ApiRequestPager
This commit is contained in:
parent
9265a06fd7
commit
41e3a19cd4
5 changed files with 25 additions and 157 deletions
|
|
@ -52,7 +52,6 @@ import com.github.damontecres.dolphin.ui.playback.isForwardButton
|
|||
import com.github.damontecres.dolphin.ui.playback.isPlayKeyUp
|
||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||
import com.github.damontecres.dolphin.util.ItemPager
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import kotlin.math.max
|
||||
|
|
@ -61,7 +60,7 @@ private const val DEBUG = false
|
|||
|
||||
@Composable
|
||||
fun CardGrid(
|
||||
pager: ItemPager,
|
||||
pager: List<BaseItem?>,
|
||||
itemOnClick: (BaseItem) -> Unit,
|
||||
longClicker: (BaseItem) -> Unit,
|
||||
letterPosition: suspend (Char) -> Int,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
|
|||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||
import com.github.damontecres.dolphin.util.ItemPager
|
||||
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -38,7 +39,7 @@ class CollectionFolderViewModel
|
|||
constructor(
|
||||
api: ApiClient,
|
||||
) : ItemViewModel<Library>(api) {
|
||||
val pager = MutableLiveData<ItemPager?>()
|
||||
val pager = MutableLiveData<ApiRequestPager<GetItemsRequest>?>()
|
||||
|
||||
override fun init(
|
||||
itemId: UUID,
|
||||
|
|
@ -81,7 +82,8 @@ class CollectionFolderViewModel
|
|||
sortOrder = listOf(SortOrder.ASCENDING),
|
||||
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO),
|
||||
)
|
||||
val newPager = ItemPager(api, request, viewModelScope)
|
||||
val newPager =
|
||||
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
|
||||
newPager.init()
|
||||
pager.value = newPager
|
||||
}
|
||||
|
|
@ -162,7 +164,7 @@ fun TVShowCollectionDetails(
|
|||
navigationManager: NavigationManager,
|
||||
library: Library,
|
||||
item: BaseItem,
|
||||
pager: ItemPager,
|
||||
pager: List<BaseItem?>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val gridFocusRequester = remember { FocusRequester() }
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
|
|||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||
import com.github.damontecres.dolphin.util.ItemPager
|
||||
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -60,7 +61,13 @@ class SeasonViewModel
|
|||
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, ItemFields.CHILD_COUNT),
|
||||
)
|
||||
val pager =
|
||||
ItemPager(api, request, viewModelScope, itemCount = item.data.childCount)
|
||||
ApiRequestPager(
|
||||
api,
|
||||
request,
|
||||
GetItemsRequestHandler,
|
||||
viewModelScope,
|
||||
itemCount = item.data.childCount,
|
||||
)
|
||||
pager.init()
|
||||
episodes.value = pager
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ import com.github.damontecres.dolphin.data.model.BaseItem
|
|||
import com.github.damontecres.dolphin.data.model.Video
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.dolphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.dolphin.util.ItemPager
|
||||
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -71,7 +70,13 @@ class SeriesViewModel
|
|||
),
|
||||
)
|
||||
val pager =
|
||||
ItemPager(api, request, viewModelScope, itemCount = item.data.childCount)
|
||||
ApiRequestPager(
|
||||
api,
|
||||
request,
|
||||
GetItemsRequestHandler,
|
||||
viewModelScope,
|
||||
itemCount = item.data.childCount,
|
||||
)
|
||||
pager.init()
|
||||
seasons.value = pager
|
||||
Timber.v("Loaded ${pager.size} seasons for series ${item.id}")
|
||||
|
|
@ -94,10 +99,7 @@ class SeriesViewModel
|
|||
viewModelScope.launch(ExceptionHandler()) {
|
||||
init(itemId, potential).join()
|
||||
season?.let { seasonNum ->
|
||||
val targetSeasonPosition =
|
||||
(seasons.value!! as ItemPager)
|
||||
.toBlockingList()
|
||||
.indexOfFirstOrNull { it.indexNumber == seasonNum }
|
||||
// TODO map season number to index in list
|
||||
loadEpisodes(seasonNum)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,142 +0,0 @@
|
|||
package com.github.damontecres.dolphin.util
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||
import com.github.damontecres.dolphin.ui.DEFAULT_PAGE_SIZE
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import timber.log.Timber
|
||||
|
||||
class ItemPager(
|
||||
val api: ApiClient,
|
||||
val request: GetItemsRequest,
|
||||
private val scope: CoroutineScope,
|
||||
private val pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||
itemCount: Int? = null,
|
||||
cacheSize: Long = 8,
|
||||
) : AbstractList<BaseItem?>() {
|
||||
private var items by mutableStateOf(ItemList<BaseItem>(0, pageSize, mapOf()))
|
||||
private var totalCount by mutableIntStateOf(itemCount ?: -1)
|
||||
private val mutex = Mutex()
|
||||
private val cachedPages =
|
||||
CacheBuilder
|
||||
.newBuilder()
|
||||
.maximumSize(cacheSize)
|
||||
.build<Int, List<BaseItem>>()
|
||||
|
||||
suspend fun init() {
|
||||
if (totalCount < 0) {
|
||||
val result =
|
||||
api.itemsApi
|
||||
.getItems(
|
||||
request.copy(
|
||||
startIndex = 0,
|
||||
limit = 1,
|
||||
enableTotalRecordCount = true,
|
||||
),
|
||||
).content
|
||||
totalCount = result.totalRecordCount
|
||||
}
|
||||
}
|
||||
|
||||
override operator fun get(index: Int): BaseItem? {
|
||||
if (index in 0..<totalCount) {
|
||||
val item = items[index]
|
||||
if (item == null) {
|
||||
fetchPage(index)
|
||||
}
|
||||
return item
|
||||
} else {
|
||||
throw IndexOutOfBoundsException("$index of $totalCount")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBlocking(position: Int): BaseItem? {
|
||||
if (position in 0..<totalCount) {
|
||||
val item = items[position]
|
||||
if (item == null) {
|
||||
fetchPage(position).join()
|
||||
return items[position]
|
||||
}
|
||||
return item
|
||||
} else {
|
||||
throw IndexOutOfBoundsException("$position of $totalCount")
|
||||
}
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = totalCount
|
||||
|
||||
private fun fetchPage(position: Int): Job =
|
||||
scope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
mutex.withLock {
|
||||
val pageNumber = position / pageSize
|
||||
if (cachedPages.getIfPresent(pageNumber) == null) {
|
||||
if (DEBUG) Timber.v("fetchPage: $pageNumber")
|
||||
val result =
|
||||
api.itemsApi
|
||||
.getItems(
|
||||
request.copy(
|
||||
startIndex = pageNumber * pageSize,
|
||||
limit = pageSize,
|
||||
enableTotalRecordCount = false,
|
||||
),
|
||||
).content
|
||||
val data = result.items.map { BaseItem.from(it, api) }
|
||||
cachedPages.put(pageNumber, data)
|
||||
items = ItemList(totalCount, pageSize, cachedPages.asMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun toBlockingList(): List<BaseItem> {
|
||||
init()
|
||||
return object : AbstractList<BaseItem>() {
|
||||
override val size: Int
|
||||
get() = totalCount
|
||||
|
||||
override fun get(index: Int): BaseItem = runBlocking { getBlocking(index)!! }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEBUG = false
|
||||
}
|
||||
|
||||
class ItemList<T>(
|
||||
val size: Int,
|
||||
val pageSize: Int,
|
||||
val pages: Map<Int, List<T>>,
|
||||
) {
|
||||
operator fun get(position: Int): T? {
|
||||
val page = position / pageSize
|
||||
val data = pages[page]
|
||||
if (data != null) {
|
||||
val index = position % pageSize
|
||||
if (index in data.indices) {
|
||||
return data[index]
|
||||
} else {
|
||||
// This can happen when items are removed while scrolling
|
||||
Timber.w(
|
||||
"Index $index not in data: position=$position, data.size=${data.size}",
|
||||
)
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue