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:
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,
|
||||
|
||||
Reference in new issue
Block a user