mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01: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.playback.isPlayKeyUp
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
@ -61,7 +60,7 @@ private const val DEBUG = false
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CardGrid(
|
fun CardGrid(
|
||||||
pager: ItemPager,
|
pager: List<BaseItem?>,
|
||||||
itemOnClick: (BaseItem) -> Unit,
|
itemOnClick: (BaseItem) -> Unit,
|
||||||
longClicker: (BaseItem) -> Unit,
|
longClicker: (BaseItem) -> Unit,
|
||||||
letterPosition: suspend (Char) -> Int,
|
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.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
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.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -38,7 +39,7 @@ class CollectionFolderViewModel
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
) : ItemViewModel<Library>(api) {
|
) : ItemViewModel<Library>(api) {
|
||||||
val pager = MutableLiveData<ItemPager?>()
|
val pager = MutableLiveData<ApiRequestPager<GetItemsRequest>?>()
|
||||||
|
|
||||||
override fun init(
|
override fun init(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
|
|
@ -81,7 +82,8 @@ class CollectionFolderViewModel
|
||||||
sortOrder = listOf(SortOrder.ASCENDING),
|
sortOrder = listOf(SortOrder.ASCENDING),
|
||||||
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO),
|
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO),
|
||||||
)
|
)
|
||||||
val newPager = ItemPager(api, request, viewModelScope)
|
val newPager =
|
||||||
|
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
|
||||||
newPager.init()
|
newPager.init()
|
||||||
pager.value = newPager
|
pager.value = newPager
|
||||||
}
|
}
|
||||||
|
|
@ -162,7 +164,7 @@ fun TVShowCollectionDetails(
|
||||||
navigationManager: NavigationManager,
|
navigationManager: NavigationManager,
|
||||||
library: Library,
|
library: Library,
|
||||||
item: BaseItem,
|
item: BaseItem,
|
||||||
pager: ItemPager,
|
pager: List<BaseItem?>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val gridFocusRequester = remember { FocusRequester() }
|
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.cards.ItemRow
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
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.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.ItemPager
|
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -60,7 +61,13 @@ class SeasonViewModel
|
||||||
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, ItemFields.CHILD_COUNT),
|
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, ItemFields.CHILD_COUNT),
|
||||||
)
|
)
|
||||||
val pager =
|
val pager =
|
||||||
ItemPager(api, request, viewModelScope, itemCount = item.data.childCount)
|
ApiRequestPager(
|
||||||
|
api,
|
||||||
|
request,
|
||||||
|
GetItemsRequestHandler,
|
||||||
|
viewModelScope,
|
||||||
|
itemCount = item.data.childCount,
|
||||||
|
)
|
||||||
pager.init()
|
pager.init()
|
||||||
episodes.value = pager
|
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.data.model.Video
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
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.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.util.ApiRequestPager
|
import com.github.damontecres.dolphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.dolphin.util.ExceptionHandler
|
import com.github.damontecres.dolphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.dolphin.util.GetEpisodesRequestHandler
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -71,7 +70,13 @@ class SeriesViewModel
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val pager =
|
val pager =
|
||||||
ItemPager(api, request, viewModelScope, itemCount = item.data.childCount)
|
ApiRequestPager(
|
||||||
|
api,
|
||||||
|
request,
|
||||||
|
GetItemsRequestHandler,
|
||||||
|
viewModelScope,
|
||||||
|
itemCount = item.data.childCount,
|
||||||
|
)
|
||||||
pager.init()
|
pager.init()
|
||||||
seasons.value = pager
|
seasons.value = pager
|
||||||
Timber.v("Loaded ${pager.size} seasons for series ${item.id}")
|
Timber.v("Loaded ${pager.size} seasons for series ${item.id}")
|
||||||
|
|
@ -94,10 +99,7 @@ class SeriesViewModel
|
||||||
viewModelScope.launch(ExceptionHandler()) {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
init(itemId, potential).join()
|
init(itemId, potential).join()
|
||||||
season?.let { seasonNum ->
|
season?.let { seasonNum ->
|
||||||
val targetSeasonPosition =
|
// TODO map season number to index in list
|
||||||
(seasons.value!! as ItemPager)
|
|
||||||
.toBlockingList()
|
|
||||||
.indexOfFirstOrNull { it.indexNumber == seasonNum }
|
|
||||||
loadEpisodes(seasonNum)
|
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