Fix GalleryList to use FastScrollLazyVerticalGrid when listMode (#324)

* Fix GalleryList to use FastScrollLazyVerticalGrid when listMode

* Fix recompose

* Initial PrefDelegate -> State<T>, MutableState<T>

* Migrate

* Optimize

---------

Co-authored-by: skymkmk <whatever@tagkong.com>
This commit is contained in:
Vladimir Dostoyevskiyandskymkmk authored and GitHub committed 2023-11-24 10:44:38 +00:00
1 parent cf42acd65c
commit b7627dc576
7 files changed
+122 -71

No files matched your search

@@ -2,6 +2,13 @@
package com.hippo.ehviewer
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import com.hippo.ehviewer.client.CHROME_USER_AGENT
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.FavListUrlBuilder
@@ -13,9 +20,78 @@ import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.map
import splitties.preferences.BoolPref
import splitties.preferences.DataStorePreferences
import splitties.preferences.FloatPref
import splitties.preferences.IntPref
import splitties.preferences.LongPref
import splitties.preferences.PrefDelegate
import splitties.preferences.StringOrNullPref
import splitties.preferences.StringPref
import splitties.preferences.StringSetOrNullPref
import splitties.preferences.StringSetPref
import splitties.preferences.edit
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T> PrefDelegate<T>.getValue(): T = when (this) {
is BoolPref -> value
is IntPref -> value
is FloatPref -> value
is LongPref -> value
is StringPref -> value
is StringOrNullPref -> value
is StringSetPref -> value
is StringSetOrNullPref -> value
} as T
@Suppress("UNCHECKED_CAST")
fun <T> PrefDelegate<T>.setValue(newValue: T) = when (this) {
is BoolPref -> value = newValue as Boolean
is IntPref -> value = newValue as Int
is FloatPref -> value = newValue as Float
is LongPref -> value = newValue as Long
is StringPref -> value = newValue as String
is StringOrNullPref -> value = newValue as? String
is StringSetPref -> value = newValue as Set<String?>
is StringSetOrNullPref -> value = newValue as? Set<String?>
}
@Stable
@Composable
inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @DisallowComposableCalls (T) -> R): State<R> {
val flow = remember { valueFlow().map { transform(it) } }
val init = getValue()
return flow.collectAsState(transform(init))
}
@Stable
@Composable
fun <T> PrefDelegate<T>.collectAsState(): State<T> {
val flow = remember { valueFlow() }
val init = getValue()
return flow.collectAsState(init)
}
@Stable
@Composable
fun <T> PrefDelegate<T>.asMutableState(): MutableState<T> {
val flow = remember { valueFlow() }
val init = getValue()
val readOnly = flow.collectAsState(init)
return remember {
object : MutableState<T> {
override var value: T
get() = readOnly.value
set(value) {
setValue(value)
}
override fun component1() = TODO()
override fun component2() = TODO()
}
}
}
object Settings : DataStorePreferences(null) {
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
@@ -25,8 +101,8 @@ object Settings : DataStorePreferences(null) {
var favCount by intArrayPref("fav_count", 10).emitTo(_favFlow)
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
val listModeBackField = intPref("list_mode_2", 0)
var listMode by listModeBackField
val listMode = intPref("list_mode_2", 0)
val detailSize = intPref("detail_size_2", 0)
val needSignInFlow: Flow<Boolean>
var downloadScheme by stringOrNullPref("image_scheme", null)
@@ -41,7 +117,6 @@ object Settings : DataStorePreferences(null) {
var preloadImage by intPref("preload_image_2", 5)
var downloadTimeout by intPref("download_timeout", 60)
var theme by intPref("theme_2", -1).observed { updateWhenThemeChanges() }
var detailSize by intPref("detail_size_2", 0)
var thumbResolution by intPref("thumb_resolution_2", 0)
var readCacheSize by intPref("read_cache_size_2", 640)
var launchPage by intPref("launch_page_2", 0)
@@ -289,10 +289,10 @@ class DownloadsScene : SearchBarScene() {
val layoutManager = AutoStaggeredGridLayoutManager(0, StaggeredGridLayoutManager.VERTICAL)
layoutManager.setColumnSize(
resources.getDimensionPixelOffset(
when (detailSize) {
when (detailSize.value) {
0 -> R.dimen.gallery_list_column_width_long
1 -> R.dimen.gallery_list_column_width_short
else -> throw IllegalStateException("Unexpected value: $detailSize")
else -> throw IllegalStateException("Unexpected value: ${detailSize.value}")
},
),
)
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text2.input.rememberTextFieldState
@@ -93,6 +94,7 @@ import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.FavListUrlBuilder
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.SadAndroid
import com.hippo.ehviewer.icons.filled.GoTo
@@ -101,7 +103,7 @@ import com.hippo.ehviewer.ui.main.FabLayout
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
import com.hippo.ehviewer.ui.startDownload
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.rememberInVM
@@ -296,17 +298,18 @@ fun FavouritesScreen(navigator: NavController) {
start = it.calculateStartPadding(layoutDirection) + marginH,
end = it.calculateEndPadding(layoutDirection) + marginH,
)
val listMode by remember {
Settings.listModeBackField.valueFlow()
}.collectAsState(Settings.listMode)
val listMode by Settings.listMode.collectAsState()
Box(modifier = Modifier.fillMaxSize()) {
if (listMode == 0) {
val height = (3 * Settings.listThumbSize * 3).pxToDp.dp
val showPages = Settings.showGalleryPages
FastScrollLazyColumn(
val columnWidth by collectDetailSizeAsState()
FastScrollLazyVerticalGrid(
columns = GridCells.Adaptive(columnWidth),
modifier = combinedModifier,
contentPadding = realPadding,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
) {
items(
count = data.itemCount,
@@ -5,8 +5,15 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.collectAsState
@Composable
fun CheckableItem(checked: Boolean, content: @Composable () -> Unit) {
@@ -21,3 +28,17 @@ fun CheckableItem(checked: Boolean, content: @Composable () -> Unit) {
}
}
}
@Composable
@Stable
fun collectDetailSizeAsState(): State<Dp> {
val long = dimensionResource(R.dimen.gallery_list_column_width_long)
val short = dimensionResource(R.dimen.gallery_list_column_width_short)
return Settings.detailSize.collectAsState {
when (it) {
0 -> long
1 -> short
else -> error("Unexpected value: $it")
}
}
}
@@ -33,6 +33,8 @@ import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
@@ -75,7 +77,6 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
@@ -145,6 +146,7 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_WHATS_HOT
import com.hippo.ehviewer.client.exception.CloudflareBypassException
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.dao.QuickSearch
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.SadAndroid
@@ -164,7 +166,7 @@ import com.hippo.ehviewer.ui.main.NormalSearch
import com.hippo.ehviewer.ui.main.SearchAdvanced
import com.hippo.ehviewer.ui.scene.GalleryListFragment.Companion.toStartArgs
import com.hippo.ehviewer.ui.tools.Deferred
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.LocalTouchSlopProvider
@@ -239,7 +241,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: NavController) {
val dialogState = LocalDialogState.current
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val listState = rememberLazyListState()
val listState = rememberLazyGridState()
val gridState = rememberLazyStaggeredGridState()
val isTopList = remember(urlBuilder) { urlBuilder.mode == MODE_TOPLIST }
val ehHint = stringResource(R.string.gallery_list_search_bar_hint_e_hentai)
@@ -299,9 +301,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: NavController) {
}
}.flow.cachedIn(viewModelScope)
}.collectAsLazyPagingItems()
val listMode by remember {
Settings.listModeBackField.valueFlow()
}.collectAsState(Settings.listMode)
val listMode by Settings.listMode.collectAsState()
val quickSearchList = remember { mutableStateListOf<QuickSearch>() }
val toplists = (stringArrayResource(id = R.array.toplist_entries) zip stringArrayResource(id = R.array.toplist_values)).toMap()
@@ -844,11 +844,14 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: NavController) {
if (listMode == 0) {
val height = (3 * Settings.listThumbSize * 3).pxToDp.dp
val showPages = Settings.showGalleryPages
FastScrollLazyColumn(
val columnWidth by collectDetailSizeAsState()
FastScrollLazyVerticalGrid(
columns = GridCells.Adaptive(columnWidth),
modifier = combinedModifier,
state = listState,
contentPadding = realPadding,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
) {
items(
count = data.itemCount,
@@ -1,52 +0,0 @@
package com.hippo.ehviewer.ui.scene
import android.view.ViewGroup
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import androidx.recyclerview.widget.RecyclerView
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.setMD3Content
import com.hippo.ehviewer.util.ExceptionUtils
class LoadStateViewHolder(
private val composeView: ComposeView,
private val retry: () -> Unit,
) : RecyclerView.ViewHolder(composeView) {
fun bind(loadState: LoadState) {
composeView.setMD3Content {
when (loadState) {
is LoadState.Loading -> {
LinearProgressIndicator()
}
is LoadState.Error -> {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = ExceptionUtils.getReadableString(loadState.error))
Button(onClick = retry) {
Text(text = stringResource(id = R.string.action_retry))
}
}
}
is LoadState.NotLoading -> Unit
}
}
}
}
class GalleryLoadStateAdapter(
private val retry: () -> Unit,
) : LoadStateAdapter<LoadStateViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState) =
LoadStateViewHolder(ComposeView(parent.context), retry)
override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) =
holder.bind(loadState)
}
@@ -34,6 +34,7 @@ import androidx.compose.ui.res.stringResource
import androidx.core.text.parseAsHtml
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhTagDatabase
@@ -236,7 +237,7 @@ fun EhScreen(navigator: DestinationsNavigator) {
title = stringResource(id = R.string.settings_eh_list_mode),
entry = R.array.list_mode_entries,
entryValueRes = R.array.list_mode_entry_values,
value = Settings::listMode.observed,
value = Settings.listMode.asMutableState(),
)
IntSliderPreference(
maxValue = 60,
@@ -249,7 +250,7 @@ fun EhScreen(navigator: DestinationsNavigator) {
title = stringResource(id = R.string.settings_eh_detail_size),
entry = R.array.detail_size_entries,
entryValueRes = R.array.detail_size_entry_values,
value = Settings::detailSize.observed,
value = Settings.detailSize.asMutableState(),
)
IntSliderPreference(
maxValue = 400,