Rewrite GalleryPreviewScreen Pager, Resolve #1152

This commit is contained in:
tarsin committed 2023-07-09 10:14:13 +08:00
1 parent 80b0203af2
commit cb9f36dd52
2 files changed
+61 -32

No files matched your search

@@ -100,3 +100,18 @@ fun EhPreviewItem(
}
Text((galleryPreview.position + 1).toString())
}
@Composable
fun EhPreviewItemPlaceholder(
position: Int,
onClick: () -> Unit,
) = Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(contentAlignment = Alignment.Center) {
CrystalCard(
onClick = onClick,
modifier = Modifier.fillMaxWidth().aspectRatio(0.6666667F),
) {
}
}
Text((position + 1).toString())
}
@@ -22,8 +22,9 @@ import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
@@ -44,7 +45,8 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.paging.compose.collectAsLazyPagingItems
import arrow.core.partially1
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import arrow.fx.coroutines.parMap
import coil.imageLoader
import com.hippo.ehviewer.R
@@ -57,18 +59,18 @@ import com.hippo.ehviewer.coil.imageRequest
import com.hippo.ehviewer.coil.justDownload
import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount
import com.hippo.ehviewer.ui.main.EhPreviewItem
import com.hippo.ehviewer.ui.main.EhPreviewItemPlaceholder
import com.hippo.ehviewer.ui.navToReader
import com.hippo.ehviewer.ui.setMD3Content
import com.hippo.ehviewer.ui.tools.rememberDialogState
import com.hippo.ehviewer.ui.tools.rememberMemorized
import com.hippo.ehviewer.util.getParcelableCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import moe.tarsin.coroutines.runSuspendCatching
import kotlin.math.roundToInt
typealias PreviewPage = List<GalleryPreview>
class GalleryPreviewScreen : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return ComposeView(inflater.context).apply {
@@ -76,7 +78,7 @@ class GalleryPreviewScreen : Fragment() {
val galleryDetail = remember { requireArguments().getParcelableCompat<GalleryDetail>(KEY_GALLERY_DETAIL)!! }
val context = LocalContext.current
fun onPreviewCLick(index: Int) = context.navToReader(galleryDetail, index)
val toNextPage = rememberSaveable { requireArguments().getBoolean(KEY_NEXT_PAGE) }
var toNextPage by rememberSaveable { mutableStateOf(requireArguments().getBoolean(KEY_NEXT_PAGE)) }
val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior()
val columnCount = calculateSuitableSpanCount()
val state = rememberLazyGridState()
@@ -85,8 +87,12 @@ class GalleryPreviewScreen : Fragment() {
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
val pages = galleryDetail.pages
val pgSize = galleryDetail.previewList.size
var initialKey by rememberSaveable { mutableIntStateOf(if (toNextPage) 2 else 1) }
val prefetchDistance = PREFETCH_THUMBS / pgSize
LaunchedEffect(Unit) {
delay(500)
if (toNextPage) state.animateScrollToItem(pgSize)
toNextPage = false
}
suspend fun getPreviewListByPage(page: Int) = galleryDetail.run {
val url = EhUrl.getGalleryDetailUrl(gid, token, page, false)
@@ -100,7 +106,7 @@ class GalleryPreviewScreen : Fragment() {
}
suspend fun showGoToDialog() {
initialKey = dialogState.show(initial = 1F, title = R.string.go_to) {
val toPage = dialogState.show(initial = 1F, title = R.string.go_to) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 18.dp)) {
Text(text = "1", modifier = Modifier.padding(12.dp))
Slider(
@@ -113,28 +119,32 @@ class GalleryPreviewScreen : Fragment() {
Text(text = pages.toString(), modifier = Modifier.padding(12.dp))
}
}.roundToInt()
val index = (toPage - 1) * pgSize
state.animateScrollToItem(index)
}
val previewPagesMap = rememberMemorized { mutableMapOf<Int, PreviewPage>().apply { put(1, galleryDetail.previewList) } }
val data = remember(initialKey) {
Pager(PagingConfig(1, prefetchDistance = prefetchDistance, enablePlaceholders = false, initialLoadSize = 1), initialKey) {
object : PagingSource<Int, PreviewPage>() {
override fun getRefreshKey(state: PagingState<Int, PreviewPage>) = null
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PreviewPage> {
val up = params.key ?: 1
val end = (up + params.loadSize - 1).coerceAtMost(galleryDetail.previewPages)
val previewPagesMap = rememberMemorized { galleryDetail.previewList.associateBy { it.position } as MutableMap }
val data = remember {
Pager(PagingConfig(pageSize = pgSize, initialLoadSize = pgSize)) {
object : PagingSource<Int, GalleryPreview>() {
override fun getRefreshKey(state: PagingState<Int, GalleryPreview>) = ((state.anchorPosition ?: 0) - state.config.initialLoadSize / 2).coerceAtLeast(0)
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GalleryPreview> {
val up = params.key ?: 0
val end = (up + params.loadSize - 1).coerceAtMost(pages - 1)
runSuspendCatching {
(up..end).mapNotNull { it.takeUnless { previewPagesMap.contains(it) } }
.parMap(Dispatchers.IO) { getPreviewListByPage(it - 1).apply { previewPagesMap[it] = this } }
(up..end).filterNot { it in previewPagesMap }.map { it / pgSize }.toSet()
.parMap(Dispatchers.IO) { getPreviewListByPage(it) }
.forEach { previews -> previews.forEach { previewPagesMap[it.position] = it } }
}.onFailure {
return LoadResult.Error(it)
}
val r = (up..end).map { previewPagesMap[it]!! }
val prevK = if (up == 1) null else up - 1
val nextK = if (end == galleryDetail.previewPages) null else end + 1
return LoadResult.Page(r, prevK, nextK)
val r = (up..end).map { requireNotNull(previewPagesMap[it]) }
val prevK = if (up == 0) null else up - 1
val nextK = if (end == pages - 1) null else end + 1
return LoadResult.Page(r, prevK, nextK, up, pages - end - 1)
}
override val keyReuseSupported = true
override val jumpingSupported = true
}
}.flow.cachedIn(lifecycleScope)
}.collectAsLazyPagingItems()
@@ -150,9 +160,7 @@ class GalleryPreviewScreen : Fragment() {
},
actions = {
if (galleryDetail.previewPages > 1) {
IconButton(onClick = {
coroutineScope.launch { showGoToDialog() }
}) {
IconButton(onClick = { coroutineScope.launch { showGoToDialog() } }) {
Icon(imageVector = Icons.Default.CallMissedOutgoing, contentDescription = null)
}
}
@@ -169,14 +177,21 @@ class GalleryPreviewScreen : Fragment() {
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
val realList = data.itemSnapshotList.items.flatten()
items(
count = realList.size,
key = { realList[it].position },
count = data.itemCount,
key = data.itemKey(key = { item -> item.position }),
contentType = data.itemContentType(),
) { index ->
data[index / pgSize] // Trigger item preload
val item = realList[index]
item.position.let { ::onPreviewCLick.partially1(it) }.let { EhPreviewItem(item, it) }
val item = data[index]
if (item != null) {
EhPreviewItem(item) {
onPreviewCLick(item.position)
}
} else {
EhPreviewItemPlaceholder(index) {
onPreviewCLick(index)
}
}
}
}
}
@@ -187,4 +202,3 @@ class GalleryPreviewScreen : Fragment() {
const val KEY_GALLERY_DETAIL = "gallery_detail"
const val KEY_NEXT_PAGE = "next_page"
private const val PREFETCH_THUMBS = 200