diff --git a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt index 946b6ff58..eb99fc179 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt @@ -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 PrefDelegate.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 PrefDelegate.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 + is StringSetOrNullPref -> value = newValue as? Set +} + +@Stable +@Composable +inline fun PrefDelegate.collectAsState(crossinline transform: @DisallowComposableCalls (T) -> R): State { + val flow = remember { valueFlow().map { transform(it) } } + val init = getValue() + return flow.collectAsState(transform(init)) +} + +@Stable +@Composable +fun PrefDelegate.collectAsState(): State { + val flow = remember { valueFlow() } + val init = getValue() + return flow.collectAsState(init) +} + +@Stable +@Composable +fun PrefDelegate.asMutableState(): MutableState { + val flow = remember { valueFlow() } + val init = getValue() + val readOnly = flow.collectAsState(init) + return remember { + object : MutableState { + 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 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) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt index ca41452c3..7eede63a5 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt @@ -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}") }, ), ) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/FavoritesFragment.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/FavoritesFragment.kt index 5e6388020..9b5a86c05 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/FavoritesFragment.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/FavoritesFragment.kt @@ -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, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryHolder.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryHolder.kt index 9964c75f0..cda831332 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryHolder.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryHolder.kt @@ -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 { + 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") + } + } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt index 56af69b73..7bfe84efa 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryListFragment.kt @@ -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() } 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, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryLoadStateAdapter.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryLoadStateAdapter.kt deleted file mode 100644 index e4f4e6e80..000000000 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/GalleryLoadStateAdapter.kt +++ /dev/null @@ -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() { - override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState) = - LoadStateViewHolder(ComposeView(parent.context), retry) - - override fun onBindViewHolder(holder: LoadStateViewHolder, loadState: LoadState) = - holder.bind(loadState) -} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt index e45bee2e9..8b6737904 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/EhScreen.kt @@ -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,