pager: do not skip next batch if current batch yields no valid apps
This commit is contained in:
11
app/src/main/java/com/aurora/store/data/PageResult.kt
Normal file
11
app/src/main/java/com/aurora/store/data/PageResult.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Aurora OSS
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.aurora.store.data
|
||||||
|
|
||||||
|
data class PageResult<T>(
|
||||||
|
val items: List<T>,
|
||||||
|
val hasMore: Boolean = items.isNotEmpty()
|
||||||
|
)
|
||||||
@@ -10,6 +10,7 @@ import androidx.paging.PagingConfig
|
|||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
|
import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
|
||||||
import androidx.paging.PagingState
|
import androidx.paging.PagingState
|
||||||
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.pager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.pager
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -23,7 +24,7 @@ import kotlinx.coroutines.withContext
|
|||||||
* @param block Data to load into the pager
|
* @param block Data to load into the pager
|
||||||
*/
|
*/
|
||||||
class GenericPagingSource<T : Any>(
|
class GenericPagingSource<T : Any>(
|
||||||
private val block: suspend (Int) -> List<T>
|
private val block: suspend (Int) -> PageResult<T>
|
||||||
) : PagingSource<Int, T>() {
|
) : PagingSource<Int, T>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -54,7 +55,7 @@ class GenericPagingSource<T : Any>(
|
|||||||
fun <T : Any> manualPager(
|
fun <T : Any> manualPager(
|
||||||
pageSize: Int = DEFAULT_PAGE_SIZE,
|
pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||||
enablePlaceholders: Boolean = true,
|
enablePlaceholders: Boolean = true,
|
||||||
data: suspend (Int) -> List<T>
|
data: suspend (Int) -> PageResult<T>
|
||||||
): Pager<Int, T> = Pager(
|
): Pager<Int, T> = Pager(
|
||||||
config = PagingConfig(enablePlaceholders = enablePlaceholders, pageSize = pageSize),
|
config = PagingConfig(enablePlaceholders = enablePlaceholders, pageSize = pageSize),
|
||||||
pagingSourceFactory = { GenericPagingSource(data) }
|
pagingSourceFactory = { GenericPagingSource(data) }
|
||||||
@@ -65,12 +66,12 @@ class GenericPagingSource<T : Any>(
|
|||||||
val page = params.key ?: 1
|
val page = params.key ?: 1
|
||||||
return try {
|
return try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val data = block(page)
|
val result = block(page)
|
||||||
LoadResult.Page(
|
LoadResult.Page(
|
||||||
data = data,
|
data = result.items,
|
||||||
prevKey = if (page == 1) null else page - 1,
|
prevKey = if (page == 1) null else page - 1,
|
||||||
nextKey = if (data.isEmpty()) null else page + 1,
|
nextKey = if (result.hasMore) page + 1 else null,
|
||||||
itemsAfter = if (data.isEmpty()) 0 else COUNT_UNDEFINED
|
itemsAfter = if (result.hasMore) COUNT_UNDEFINED else 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|||||||
@@ -20,14 +20,13 @@
|
|||||||
package com.aurora.store.viewmodel.all
|
package com.aurora.store.viewmodel.all
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import com.aurora.extensions.TAG
|
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper
|
||||||
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
import com.aurora.store.data.providers.BlacklistProvider
|
import com.aurora.store.data.providers.BlacklistProvider
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
@@ -63,14 +62,11 @@ class InstalledViewModel @Inject constructor(
|
|||||||
.chunked(20)
|
.chunked(20)
|
||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
try {
|
// page is 1-indexed, but list is 0-indexed
|
||||||
// page is 1-indexed, but list is 0-indexed
|
val chunk = pagedPackages.getOrNull(page - 1)
|
||||||
val chunk = pagedPackages.getOrNull(page - 1) ?: return@manualPager emptyList()
|
?: return@manualPager PageResult(emptyList<App>(), hasMore = false)
|
||||||
webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
|
val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName })
|
||||||
} catch (exception: Exception) {
|
PageResult(items, hasMore = page < pagedPackages.size)
|
||||||
Log.e(TAG, "Failed to fetch apps", exception)
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}.flow.distinctUntilChanged()
|
}.flow.distinctUntilChanged()
|
||||||
.cachedIn(viewModelScope)
|
.cachedIn(viewModelScope)
|
||||||
.onEach { _apps.value = it }
|
.onEach { _apps.value = it }
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.aurora.extensions.TAG
|
|||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.StreamCluster
|
import com.aurora.gplayapi.data.models.StreamCluster
|
||||||
import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
||||||
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
@@ -61,7 +62,7 @@ class StreamBrowseViewModel @AssistedInject constructor(
|
|||||||
var nextPageUrl: String = streamCluster.clusterNextPageUrl
|
var nextPageUrl: String = streamCluster.clusterNextPageUrl
|
||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
try {
|
val items = try {
|
||||||
when (page) {
|
when (page) {
|
||||||
1 -> streamCluster.clusterAppList
|
1 -> streamCluster.clusterAppList
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ class StreamBrowseViewModel @AssistedInject constructor(
|
|||||||
Log.e(TAG, "Failed to fetch apps for $page: $nextPageUrl", exception)
|
Log.e(TAG, "Failed to fetch apps for $page: $nextPageUrl", exception)
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
PageResult(items)
|
||||||
}.flow.distinctUntilChanged()
|
}.flow.distinctUntilChanged()
|
||||||
.cachedIn(viewModelScope)
|
.cachedIn(viewModelScope)
|
||||||
.onEach { _apps.value = it }
|
.onEach { _apps.value = it }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.paging.cachedIn
|
|||||||
import com.aurora.extensions.TAG
|
import com.aurora.extensions.TAG
|
||||||
import com.aurora.gplayapi.data.models.Review
|
import com.aurora.gplayapi.data.models.Review
|
||||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||||
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
@@ -46,7 +47,7 @@ class ReviewViewModel @AssistedInject constructor(
|
|||||||
var reviewsNextPageUrl: String? = null
|
var reviewsNextPageUrl: String? = null
|
||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
try {
|
val items = try {
|
||||||
when (page) {
|
when (page) {
|
||||||
1 -> reviewsHelper.getReviews(packageName, filter).also {
|
1 -> reviewsHelper.getReviews(packageName, filter).also {
|
||||||
reviewsNextPageUrl = it.nextPageUrl
|
reviewsNextPageUrl = it.nextPageUrl
|
||||||
@@ -66,6 +67,7 @@ class ReviewViewModel @AssistedInject constructor(
|
|||||||
Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception)
|
Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception)
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
PageResult(items)
|
||||||
}.flow.distinctUntilChanged()
|
}.flow.distinctUntilChanged()
|
||||||
.cachedIn(viewModelScope)
|
.cachedIn(viewModelScope)
|
||||||
.onEach { _reviews.value = it }
|
.onEach { _reviews.value = it }
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.aurora.gplayapi.data.models.StreamCluster
|
|||||||
import com.aurora.gplayapi.helpers.SearchHelper
|
import com.aurora.gplayapi.helpers.SearchHelper
|
||||||
import com.aurora.gplayapi.helpers.contracts.SearchContract
|
import com.aurora.gplayapi.helpers.contracts.SearchContract
|
||||||
import com.aurora.gplayapi.helpers.web.WebSearchHelper
|
import com.aurora.gplayapi.helpers.web.WebSearchHelper
|
||||||
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.model.SearchFilter
|
import com.aurora.store.data.model.SearchFilter
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
@@ -80,7 +81,7 @@ class SearchViewModel @Inject constructor(
|
|||||||
}.distinctBy { app -> app.packageName }
|
}.distinctBy { app -> app.packageName }
|
||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
try {
|
val items = try {
|
||||||
when (page) {
|
when (page) {
|
||||||
1 -> contract.searchResults(query)
|
1 -> contract.searchResults(query)
|
||||||
.also { nextBundleUrl = it.streamNextPageUrl }
|
.also { nextBundleUrl = it.streamNextPageUrl }
|
||||||
@@ -110,6 +111,7 @@ class SearchViewModel @Inject constructor(
|
|||||||
Log.e(TAG, "Failed to search results for $query", exception)
|
Log.e(TAG, "Failed to search results for $query", exception)
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
PageResult(items)
|
||||||
}.flow.distinctUntilChanged()
|
}.flow.distinctUntilChanged()
|
||||||
.cachedIn(viewModelScope)
|
.cachedIn(viewModelScope)
|
||||||
.onEach { _apps.value = it }
|
.onEach { _apps.value = it }
|
||||||
|
|||||||
Reference in New Issue
Block a user