From 5f0276901283a8cee0b2f204c142b13d5ca7cd68 Mon Sep 17 00:00:00 2001 From: Lokesh Gidra Date: Thu, 2 Nov 2023 13:10:56 +0800 Subject: [PATCH] Extract a fragmentless GalleryPreviewScreen --- .../ui/scene/GalleryPreviewFragment.kt | 180 ++++++++++++++++++ .../ehviewer/ui/scene/GalleryPreviewScreen.kt | 169 ---------------- app/src/main/res/navigation/nav_graph.xml | 2 +- 3 files changed, 181 insertions(+), 170 deletions(-) create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewFragment.kt delete mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewFragment.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewFragment.kt new file mode 100644 index 000000000..f3dcdc495 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewFragment.kt @@ -0,0 +1,180 @@ +package com.hippo.ehviewer.ui.scene + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.fragment.app.Fragment +import androidx.lifecycle.viewModelScope +import androidx.navigation.NavController +import androidx.navigation.findNavController +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.paging.cachedIn +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey +import androidx.room.paging.util.getClippedRefreshKey +import androidx.room.paging.util.getLimit +import androidx.room.paging.util.getOffset +import arrow.fx.coroutines.parMap +import coil.imageLoader +import com.hippo.ehviewer.R +import com.hippo.ehviewer.Settings +import com.hippo.ehviewer.client.EhEngine +import com.hippo.ehviewer.client.EhUrl +import com.hippo.ehviewer.client.data.GalleryDetail +import com.hippo.ehviewer.client.data.GalleryPreview +import com.hippo.ehviewer.coil.justDownload +import com.hippo.ehviewer.ktbuilder.imageRequest +import com.hippo.ehviewer.ui.MainActivity +import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount +import com.hippo.ehviewer.ui.main.EhPreviewItem +import com.hippo.ehviewer.ui.navToReader +import com.hippo.ehviewer.ui.setMD3Content +import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid +import com.hippo.ehviewer.ui.tools.rememberInVM +import com.hippo.ehviewer.util.findActivity +import com.hippo.ehviewer.util.getParcelableCompat +import com.ramcosta.composedestinations.annotation.Destination +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import moe.tarsin.coroutines.runSuspendCatching + +@Destination +@Composable +fun GalleryPreviewScreen(galleryDetail: GalleryDetail, toNextPageArg: Boolean, navigator: NavController) { + val context = LocalContext.current + fun onPreviewClick(index: Int) = context.navToReader(galleryDetail.galleryInfo, index) + val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior() + val columnCount = calculateSuitableSpanCount() + val state = rememberLazyGridState() + val coroutineScope = rememberCoroutineScope { Dispatchers.IO } + val pages = galleryDetail.pages + val pgSize = galleryDetail.previewList.size + var toNextPage by rememberSaveable { mutableStateOf(toNextPageArg) } + + LaunchedEffect(Unit) { + if (toNextPage) state.scrollToItem(pgSize) + toNextPage = false + } + + suspend fun getPreviewListByPage(page: Int) = galleryDetail.run { + val url = EhUrl.getGalleryDetailUrl(gid, token, page, false) + val result = EhEngine.getPreviewList(url) + if (Settings.preloadThumbAggressively) { + coroutineScope.launch { + context.run { result.first.first.forEach { imageLoader.enqueue(imageRequest(it) { justDownload() }) } } + } + } + result.first.first + } + + val data = rememberInVM { + val previewPagesMap = galleryDetail.previewList.associateBy { it.position } as MutableMap + Pager(PagingConfig(pageSize = pgSize, initialLoadSize = pgSize, jumpThreshold = 2 * pgSize)) { + object : PagingSource() { + override fun getRefreshKey(state: PagingState) = state.getClippedRefreshKey() + override suspend fun load(params: LoadParams): LoadResult { + val key = params.key ?: 0 + val up = getOffset(params, key, pages) + val end = (up + getLimit(params, key) - 1).coerceAtMost(pages - 1) + runSuspendCatching { + (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 { requireNotNull(previewPagesMap[it]) } + val prevK = if (up <= 0 || r.isEmpty()) null else up + val nextK = if (end == pages - 1) null else end + 1 + return LoadResult.Page(r, prevK, nextK, up, pages - end - 1) + } + override val jumpingSupported = true + } + }.flow.cachedIn(viewModelScope) + }.collectAsLazyPagingItems() + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.gallery_previews)) }, + navigationIcon = { + IconButton(onClick = { navigator.popBackStack() }) { + Icon(imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = null) + } + }, + scrollBehavior = scrollBehaviour, + ) + }, + ) { paddingValues -> + FastScrollLazyVerticalGrid( + columns = GridCells.Fixed(columnCount), + modifier = Modifier.nestedScroll(scrollBehaviour.nestedScrollConnection).padding(horizontal = dimensionResource(id = R.dimen.gallery_list_margin_h)), + state = state, + contentPadding = paddingValues, + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + items( + count = data.itemCount, + key = data.itemKey(key = { item -> item.position }), + contentType = data.itemContentType(), + ) { index -> + val item = data[index] + EhPreviewItem(item, index) { + onPreviewClick(index) + } + } + } + } +} + +class GalleryPreviewFragment : Fragment() { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = ComposeView(inflater.context).apply { + setMD3Content { + val galleryDetail = remember { requireArguments().getParcelableCompat(KEY_GALLERY_DETAIL)!! } + val toNextPage = remember { requireArguments().getBoolean(KEY_NEXT_PAGE) } + val navController = remember { findNavController() } + GalleryPreviewScreen(galleryDetail, toNextPage, navController) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + requireContext().findActivity().recompose() + } +} + +const val KEY_GALLERY_DETAIL = "gallery_detail" +const val KEY_NEXT_PAGE = "next_page" diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt deleted file mode 100644 index 19f3ec6bc..000000000 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryPreviewScreen.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.hippo.ehviewer.ui.scene - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold -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.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.fragment.app.Fragment -import androidx.lifecycle.viewModelScope -import androidx.navigation.fragment.findNavController -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingSource -import androidx.paging.PagingState -import androidx.paging.cachedIn -import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.compose.itemContentType -import androidx.paging.compose.itemKey -import androidx.room.paging.util.getClippedRefreshKey -import androidx.room.paging.util.getLimit -import androidx.room.paging.util.getOffset -import arrow.fx.coroutines.parMap -import coil.imageLoader -import com.hippo.ehviewer.R -import com.hippo.ehviewer.Settings -import com.hippo.ehviewer.client.EhEngine -import com.hippo.ehviewer.client.EhUrl -import com.hippo.ehviewer.client.data.GalleryDetail -import com.hippo.ehviewer.client.data.GalleryPreview -import com.hippo.ehviewer.coil.justDownload -import com.hippo.ehviewer.ktbuilder.imageRequest -import com.hippo.ehviewer.ui.MainActivity -import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount -import com.hippo.ehviewer.ui.main.EhPreviewItem -import com.hippo.ehviewer.ui.navToReader -import com.hippo.ehviewer.ui.setMD3Content -import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid -import com.hippo.ehviewer.ui.tools.rememberInVM -import com.hippo.ehviewer.util.findActivity -import com.hippo.ehviewer.util.getParcelableCompat -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import moe.tarsin.coroutines.runSuspendCatching - -class GalleryPreviewScreen : Fragment() { - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = ComposeView(inflater.context).apply { - setMD3Content { - val galleryDetail = remember { requireArguments().getParcelableCompat(KEY_GALLERY_DETAIL)!! } - val context = LocalContext.current - fun onPreviewCLick(index: Int) = context.navToReader(galleryDetail.galleryInfo, index) - var toNextPage by rememberSaveable { mutableStateOf(requireArguments().getBoolean(KEY_NEXT_PAGE)) } - val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior() - val columnCount = calculateSuitableSpanCount() - val state = rememberLazyGridState() - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - val pages = galleryDetail.pages - val pgSize = galleryDetail.previewList.size - - LaunchedEffect(Unit) { - if (toNextPage) state.scrollToItem(pgSize) - toNextPage = false - } - - suspend fun getPreviewListByPage(page: Int) = galleryDetail.run { - val url = EhUrl.getGalleryDetailUrl(gid, token, page, false) - val result = EhEngine.getPreviewList(url) - if (Settings.preloadThumbAggressively) { - coroutineScope.launch { - context.run { result.first.first.forEach { imageLoader.enqueue(imageRequest(it) { justDownload() }) } } - } - } - result.first.first - } - - val data = rememberInVM { - val previewPagesMap = galleryDetail.previewList.associateBy { it.position } as MutableMap - Pager(PagingConfig(pageSize = pgSize, initialLoadSize = pgSize, jumpThreshold = 2 * pgSize)) { - object : PagingSource() { - override fun getRefreshKey(state: PagingState) = state.getClippedRefreshKey() - override suspend fun load(params: LoadParams): LoadResult { - val key = params.key ?: 0 - val up = getOffset(params, key, pages) - val end = (up + getLimit(params, key) - 1).coerceAtMost(pages - 1) - runSuspendCatching { - (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 { requireNotNull(previewPagesMap[it]) } - val prevK = if (up <= 0 || r.isEmpty()) null else up - val nextK = if (end == pages - 1) null else end + 1 - return LoadResult.Page(r, prevK, nextK, up, pages - end - 1) - } - override val jumpingSupported = true - } - }.flow.cachedIn(viewModelScope) - }.collectAsLazyPagingItems() - - Scaffold( - topBar = { - TopAppBar( - title = { Text(stringResource(R.string.gallery_previews)) }, - navigationIcon = { - IconButton(onClick = { findNavController().popBackStack() }) { - Icon(imageVector = Icons.AutoMirrored.Default.ArrowBack, contentDescription = null) - } - }, - scrollBehavior = scrollBehaviour, - ) - }, - ) { paddingValues -> - FastScrollLazyVerticalGrid( - columns = GridCells.Fixed(columnCount), - modifier = Modifier.nestedScroll(scrollBehaviour.nestedScrollConnection).padding(horizontal = dimensionResource(id = R.dimen.gallery_list_margin_h)), - state = state, - contentPadding = paddingValues, - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - ) { - items( - count = data.itemCount, - key = data.itemKey(key = { item -> item.position }), - contentType = data.itemContentType(), - ) { index -> - val item = data[index] - EhPreviewItem(item, index) { - onPreviewCLick(index) - } - } - } - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - requireContext().findActivity().recompose() - } -} - -const val KEY_GALLERY_DETAIL = "gallery_detail" -const val KEY_NEXT_PAGE = "next_page" diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 831453156..7f2aed91c 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -53,7 +53,7 @@ android:label="GalleryCommentsScene" />