mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-28 20:03:34 +00:00
improved trakt saving to list behaviour
This commit is contained in:
parent
99f251ac4e
commit
3a8e0e9d87
5 changed files with 146 additions and 54 deletions
|
|
@ -1106,7 +1106,7 @@ private fun MainAppContent(
|
||||||
NuvioPosterActionSheet(
|
NuvioPosterActionSheet(
|
||||||
item = selectedPosterForActions,
|
item = selectedPosterForActions,
|
||||||
isSaved = selectedPosterForActions?.let { preview ->
|
isSaved = selectedPosterForActions?.let { preview ->
|
||||||
LibraryRepository.isSaved(preview.id)
|
LibraryRepository.isSaved(preview.id, preview.type)
|
||||||
} == true,
|
} == true,
|
||||||
isWatched = selectedPosterForActions?.let { preview ->
|
isWatched = selectedPosterForActions?.let { preview ->
|
||||||
WatchingState.isPosterWatched(
|
WatchingState.isPosterWatched(
|
||||||
|
|
@ -1123,9 +1123,12 @@ private fun MainAppContent(
|
||||||
} else {
|
} else {
|
||||||
pickerItem = libraryItem
|
pickerItem = libraryItem
|
||||||
pickerTitle = preview.name
|
pickerTitle = preview.name
|
||||||
|
pickerTabs = LibraryRepository.traktListTabs()
|
||||||
|
pickerMembership = pickerTabs.associate { it.key to false }
|
||||||
|
pickerPending = true
|
||||||
|
pickerError = null
|
||||||
|
showLibraryListPicker = true
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
pickerPending = true
|
|
||||||
pickerError = null
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
|
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
|
||||||
val tabs = LibraryRepository.traktListTabs()
|
val tabs = LibraryRepository.traktListTabs()
|
||||||
|
|
@ -1133,7 +1136,6 @@ private fun MainAppContent(
|
||||||
pickerMembership = tabs.associate { tab ->
|
pickerMembership = tabs.associate { tab ->
|
||||||
tab.key to (snapshot[tab.key] == true)
|
tab.key to (snapshot[tab.key] == true)
|
||||||
}
|
}
|
||||||
showLibraryListPicker = true
|
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
pickerError = error.message ?: "Failed to load Trakt lists"
|
pickerError = error.message ?: "Failed to load Trakt lists"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
|
@ -74,41 +75,65 @@ fun TraktListPickerDialog(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyColumn(
|
if (isPending && tabs.isEmpty()) {
|
||||||
modifier = Modifier
|
Box(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.height(280.dp),
|
.fillMaxWidth()
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
.height(280.dp),
|
||||||
) {
|
contentAlignment = Alignment.Center,
|
||||||
items(items = tabs, key = { it.key }) { tab ->
|
) {
|
||||||
val selected = membership[tab.key] == true
|
Column(
|
||||||
Row(
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
color = if (selected) {
|
|
||||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.14f)
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
)
|
|
||||||
.clickable(enabled = !isPending) { onToggle(tab.key) }
|
|
||||||
.padding(horizontal = 14.dp, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
) {
|
||||||
Text(
|
CircularProgressIndicator(
|
||||||
text = tab.title,
|
strokeWidth = 2.dp,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.size(24.dp),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
)
|
||||||
if (selected) {
|
Text(
|
||||||
androidx.compose.material3.Icon(
|
text = "Loading your Trakt lists…",
|
||||||
imageVector = Icons.Rounded.Check,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
contentDescription = null,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(280.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
items(items = tabs, key = { it.key }) { tab ->
|
||||||
|
val selected = membership[tab.key] == true
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = if (selected) {
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.14f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
)
|
||||||
|
.clickable(enabled = !isPending) { onToggle(tab.key) }
|
||||||
|
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = tab.title,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
)
|
)
|
||||||
|
if (selected) {
|
||||||
|
androidx.compose.material3.Icon(
|
||||||
|
imageVector = Icons.Rounded.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,8 +213,14 @@ fun MetaDetailsScreen(
|
||||||
displayedMeta != null -> {
|
displayedMeta != null -> {
|
||||||
val meta = displayedMeta
|
val meta = displayedMeta
|
||||||
val todayIsoDate = CurrentDateProvider.todayIsoDate()
|
val todayIsoDate = CurrentDateProvider.todayIsoDate()
|
||||||
val isSaved = remember(libraryUiState.items, meta.id) {
|
val isSaved = remember(
|
||||||
libraryUiState.items.any { it.id == meta.id }
|
libraryUiState.items,
|
||||||
|
libraryUiState.sections,
|
||||||
|
traktAuthUiState.mode,
|
||||||
|
meta.id,
|
||||||
|
meta.type,
|
||||||
|
) {
|
||||||
|
LibraryRepository.isSaved(meta.id, meta.type)
|
||||||
}
|
}
|
||||||
val isTraktConnected = traktAuthUiState.mode == TraktConnectionMode.CONNECTED
|
val isTraktConnected = traktAuthUiState.mode == TraktConnectionMode.CONNECTED
|
||||||
val toggleSaved = remember(meta, isTraktConnected) {
|
val toggleSaved = remember(meta, isTraktConnected) {
|
||||||
|
|
@ -223,17 +229,19 @@ fun MetaDetailsScreen(
|
||||||
if (!isTraktConnected) {
|
if (!isTraktConnected) {
|
||||||
LibraryRepository.toggleSaved(libraryItem)
|
LibraryRepository.toggleSaved(libraryItem)
|
||||||
} else {
|
} else {
|
||||||
|
pickerTabs = LibraryRepository.traktListTabs()
|
||||||
|
pickerMembership = pickerTabs.associate { it.key to false }
|
||||||
|
pickerPending = true
|
||||||
|
pickerError = null
|
||||||
|
showLibraryListPicker = true
|
||||||
detailsScope.launch {
|
detailsScope.launch {
|
||||||
pickerPending = true
|
|
||||||
pickerError = null
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
|
val snapshot = LibraryRepository.getMembershipSnapshot(libraryItem)
|
||||||
val tabs = LibraryRepository.traktListTabs()
|
val tabs = LibraryRepository.traktListTabs()
|
||||||
pickerTabs = tabs
|
pickerTabs = tabs
|
||||||
pickerMembership = tabs.associate { tab ->
|
pickerMembership = tabs.associate { tab ->
|
||||||
tab.key to (snapshot[tab.key] == true)
|
tab.key to (snapshot[tab.key] == true)
|
||||||
}
|
}
|
||||||
showLibraryListPicker = true
|
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
pickerError = error.message ?: "Failed to load Trakt lists"
|
pickerError = error.message ?: "Failed to load Trakt lists"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ object LibraryRepository {
|
||||||
syncScope.launch {
|
syncScope.launch {
|
||||||
TraktAuthRepository.isAuthenticated.collectLatest { authenticated ->
|
TraktAuthRepository.isAuthenticated.collectLatest { authenticated ->
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
|
TraktLibraryRepository.preloadListTabsAsync()
|
||||||
runCatching { TraktLibraryRepository.refreshNow() }
|
runCatching { TraktLibraryRepository.refreshNow() }
|
||||||
.onFailure { log.e(it) { "Failed to refresh Trakt library after auth change" } }
|
.onFailure { log.e(it) { "Failed to refresh Trakt library after auth change" } }
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +86,7 @@ object LibraryRepository {
|
||||||
if (hasLoaded) return
|
if (hasLoaded) return
|
||||||
loadFromDisk(ProfileRepository.activeProfileId)
|
loadFromDisk(ProfileRepository.activeProfileId)
|
||||||
if (TraktAuthRepository.isAuthenticated.value) {
|
if (TraktAuthRepository.isAuthenticated.value) {
|
||||||
|
TraktLibraryRepository.preloadListTabsAsync()
|
||||||
refreshTraktLibraryAsync()
|
refreshTraktLibraryAsync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +97,7 @@ object LibraryRepository {
|
||||||
TraktAuthRepository.onProfileChanged()
|
TraktAuthRepository.onProfileChanged()
|
||||||
TraktLibraryRepository.onProfileChanged()
|
TraktLibraryRepository.onProfileChanged()
|
||||||
if (TraktAuthRepository.isAuthenticated.value) {
|
if (TraktAuthRepository.isAuthenticated.value) {
|
||||||
|
TraktLibraryRepository.preloadListTabsAsync()
|
||||||
refreshTraktLibraryAsync()
|
refreshTraktLibraryAsync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -189,10 +192,13 @@ object LibraryRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSaved(id: String): Boolean {
|
fun isSaved(id: String, type: String? = null): Boolean {
|
||||||
ensureLoaded()
|
ensureLoaded()
|
||||||
|
|
||||||
if (TraktAuthRepository.isAuthenticated.value) {
|
if (TraktAuthRepository.isAuthenticated.value) {
|
||||||
|
if (type != null) {
|
||||||
|
return TraktLibraryRepository.isInAnyList(id, type)
|
||||||
|
}
|
||||||
val entry = TraktLibraryRepository.uiState.value.allItems.firstOrNull { it.id == id }
|
val entry = TraktLibraryRepository.uiState.value.allItems.firstOrNull { it.id == id }
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
return TraktLibraryRepository.isInAnyList(entry.id, entry.type)
|
return TraktLibraryRepository.isInAnyList(entry.id, entry.type)
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,17 @@ import com.nuvio.app.features.addons.httpPostJsonWithHeaders
|
||||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||||
import com.nuvio.app.features.library.LibraryItem
|
import com.nuvio.app.features.library.LibraryItem
|
||||||
import com.nuvio.app.features.tmdb.TmdbService
|
import com.nuvio.app.features.tmdb.TmdbService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
@ -33,6 +36,7 @@ private const val PERSONAL_LIST_PREFIX = "trakt:list:"
|
||||||
private const val METADATA_FETCH_TIMEOUT_MS = 3_500L
|
private const val METADATA_FETCH_TIMEOUT_MS = 3_500L
|
||||||
private const val METADATA_FETCH_CONCURRENCY = 5
|
private const val METADATA_FETCH_CONCURRENCY = 5
|
||||||
private const val SNAPSHOT_CACHE_TTL_MS = 60_000L
|
private const val SNAPSHOT_CACHE_TTL_MS = 60_000L
|
||||||
|
private const val LIST_TABS_CACHE_TTL_MS = 60_000L
|
||||||
|
|
||||||
data class TraktLibraryUiState(
|
data class TraktLibraryUiState(
|
||||||
val listTabs: List<TraktListTab> = emptyList(),
|
val listTabs: List<TraktListTab> = emptyList(),
|
||||||
|
|
@ -46,6 +50,7 @@ data class TraktLibraryUiState(
|
||||||
object TraktLibraryRepository {
|
object TraktLibraryRepository {
|
||||||
private val log = Logger.withTag("TraktLibrary")
|
private val log = Logger.withTag("TraktLibrary")
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(TraktLibraryUiState())
|
private val _uiState = MutableStateFlow(TraktLibraryUiState())
|
||||||
val uiState: StateFlow<TraktLibraryUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<TraktLibraryUiState> = _uiState.asStateFlow()
|
||||||
|
|
@ -53,20 +58,37 @@ object TraktLibraryRepository {
|
||||||
private var hasLoaded = false
|
private var hasLoaded = false
|
||||||
private val refreshMutex = Mutex()
|
private val refreshMutex = Mutex()
|
||||||
private var lastRefreshAtMs: Long = 0L
|
private var lastRefreshAtMs: Long = 0L
|
||||||
|
private var lastListTabsRefreshAtMs: Long = 0L
|
||||||
|
|
||||||
fun ensureLoaded() {
|
fun ensureLoaded() {
|
||||||
if (hasLoaded) return
|
if (hasLoaded) return
|
||||||
hasLoaded = true
|
hasLoaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun preloadListTabsAsync() {
|
||||||
|
if (!TraktAuthRepository.isAuthenticated.value) return
|
||||||
|
if (_uiState.value.listTabs.isNotEmpty()) return
|
||||||
|
scope.launch {
|
||||||
|
runCatching { preloadListTabs() }
|
||||||
|
.onFailure { error ->
|
||||||
|
if (error is CancellationException) throw error
|
||||||
|
log.w { "Failed to preload Trakt list tabs: ${error.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onProfileChanged() {
|
fun onProfileChanged() {
|
||||||
hasLoaded = false
|
hasLoaded = false
|
||||||
|
lastRefreshAtMs = 0L
|
||||||
|
lastListTabsRefreshAtMs = 0L
|
||||||
_uiState.value = TraktLibraryUiState()
|
_uiState.value = TraktLibraryUiState()
|
||||||
ensureLoaded()
|
ensureLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearLocalState() {
|
fun clearLocalState() {
|
||||||
hasLoaded = false
|
hasLoaded = false
|
||||||
|
lastRefreshAtMs = 0L
|
||||||
|
lastListTabsRefreshAtMs = 0L
|
||||||
_uiState.value = TraktLibraryUiState()
|
_uiState.value = TraktLibraryUiState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,6 +107,21 @@ object TraktLibraryRepository {
|
||||||
refresh(force = false)
|
refresh(force = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun preloadListTabs() {
|
||||||
|
ensureLoaded()
|
||||||
|
refreshMutex.withLock {
|
||||||
|
if (_uiState.value.listTabs.isNotEmpty()) return
|
||||||
|
|
||||||
|
val headers = TraktAuthRepository.authorizedHeaders() ?: return
|
||||||
|
val tabs = fetchListTabs(headers)
|
||||||
|
_uiState.value = _uiState.value.copy(
|
||||||
|
listTabs = tabs,
|
||||||
|
errorMessage = null,
|
||||||
|
)
|
||||||
|
lastListTabsRefreshAtMs = TraktPlatformClock.nowEpochMs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun refresh(force: Boolean) {
|
private suspend fun refresh(force: Boolean) {
|
||||||
ensureLoaded()
|
ensureLoaded()
|
||||||
refreshMutex.withLock {
|
refreshMutex.withLock {
|
||||||
|
|
@ -107,6 +144,7 @@ object TraktLibraryRepository {
|
||||||
if (headers == null) {
|
if (headers == null) {
|
||||||
_uiState.value = TraktLibraryUiState()
|
_uiState.value = TraktLibraryUiState()
|
||||||
lastRefreshAtMs = 0L
|
lastRefreshAtMs = 0L
|
||||||
|
lastListTabsRefreshAtMs = 0L
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,21 +318,23 @@ object TraktLibraryRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchSnapshot(headers: Map<String, String>): TraktLibraryUiState = withContext(Dispatchers.Default) {
|
private suspend fun fetchSnapshot(headers: Map<String, String>): TraktLibraryUiState = withContext(Dispatchers.Default) {
|
||||||
val watchlistTabs = listOf(
|
val now = TraktPlatformClock.nowEpochMs()
|
||||||
TraktListTab(
|
val cachedTabs = _uiState.value.listTabs
|
||||||
key = WATCHLIST_KEY,
|
val allTabs = if (
|
||||||
title = "Watchlist",
|
cachedTabs.isNotEmpty() &&
|
||||||
type = TraktListType.WATCHLIST,
|
now - lastListTabsRefreshAtMs <= LIST_TABS_CACHE_TTL_MS
|
||||||
),
|
) {
|
||||||
)
|
cachedTabs
|
||||||
|
} else {
|
||||||
val personalLists = fetchPersonalLists(headers)
|
fetchListTabs(headers).also {
|
||||||
val allTabs = watchlistTabs + personalLists
|
lastListTabsRefreshAtMs = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val entriesByList = linkedMapOf<String, List<LibraryItem>>()
|
val entriesByList = linkedMapOf<String, List<LibraryItem>>()
|
||||||
entriesByList[WATCHLIST_KEY] = fetchWatchlistItems(headers)
|
entriesByList[WATCHLIST_KEY] = fetchWatchlistItems(headers)
|
||||||
|
|
||||||
personalLists.forEach { tab ->
|
allTabs.filter { it.type == TraktListType.PERSONAL }.forEach { tab ->
|
||||||
val listId = tab.traktListId?.toString() ?: return@forEach
|
val listId = tab.traktListId?.toString() ?: return@forEach
|
||||||
entriesByList[tab.key] = fetchPersonalListItems(headers, listId)
|
entriesByList[tab.key] = fetchPersonalListItems(headers, listId)
|
||||||
}
|
}
|
||||||
|
|
@ -323,6 +363,17 @@ object TraktLibraryRepository {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchListTabs(headers: Map<String, String>): List<TraktListTab> {
|
||||||
|
val watchlistTabs = listOf(
|
||||||
|
TraktListTab(
|
||||||
|
key = WATCHLIST_KEY,
|
||||||
|
title = "Watchlist",
|
||||||
|
type = TraktListType.WATCHLIST,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return watchlistTabs + fetchPersonalLists(headers)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun hydrateEntriesFromAddonMeta(
|
private suspend fun hydrateEntriesFromAddonMeta(
|
||||||
entriesByList: Map<String, List<LibraryItem>>,
|
entriesByList: Map<String, List<LibraryItem>>,
|
||||||
): Map<String, List<LibraryItem>> = coroutineScope {
|
): Map<String, List<LibraryItem>> = coroutineScope {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue