Эх сурвалжийг харах

Replace our custom Pager (#9494)

Turns out that changing the pagerSnapDistance
is enough to achieve the same result.
Ivan Iskandar 1 жил өмнө
parent
commit
96defd6b05

+ 2 - 2
app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt

@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.MoreVert
 import androidx.compose.material3.Icon
@@ -31,7 +32,6 @@ import kotlinx.coroutines.launch
 import tachiyomi.presentation.core.components.HorizontalPager
 import tachiyomi.presentation.core.components.material.Divider
 import tachiyomi.presentation.core.components.material.TabIndicator
-import tachiyomi.presentation.core.components.rememberPagerState
 
 object TabbedDialogPaddings {
     val Horizontal = 24.dp
@@ -84,7 +84,7 @@ fun TabbedDialog(
 
             HorizontalPager(
                 modifier = Modifier.animateContentSize(),
-                count = tabTitles.size,
+                pageCount = tabTitles.size,
                 state = pagerState,
                 verticalAlignment = Alignment.Top,
             ) { page ->

+ 2 - 2
app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt

@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
@@ -25,7 +26,6 @@ import tachiyomi.presentation.core.components.HorizontalPager
 import tachiyomi.presentation.core.components.material.Scaffold
 import tachiyomi.presentation.core.components.material.TabIndicator
 import tachiyomi.presentation.core.components.material.TabText
-import tachiyomi.presentation.core.components.rememberPagerState
 
 @Composable
 fun TabbedScreen(
@@ -82,7 +82,7 @@ fun TabbedScreen(
             }
 
             HorizontalPager(
-                count = tabs.size,
+                pageCount = tabs.size,
                 modifier = Modifier.fillMaxSize(),
                 state = state,
                 verticalAlignment = Alignment.Top,

+ 5 - 3
app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -22,7 +23,6 @@ import tachiyomi.domain.category.model.Category
 import tachiyomi.domain.library.model.LibraryDisplayMode
 import tachiyomi.domain.library.model.LibraryManga
 import tachiyomi.presentation.core.components.material.PullRefresh
-import tachiyomi.presentation.core.components.rememberPagerState
 import kotlin.time.Duration.Companion.seconds
 
 @Composable
@@ -60,8 +60,10 @@ fun LibraryContent(
         var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
 
         if (showPageTabs && categories.size > 1) {
-            if (categories.size <= pagerState.currentPage) {
-                pagerState.currentPage = categories.size - 1
+            LaunchedEffect(categories) {
+                if (categories.size <= pagerState.currentPage) {
+                    pagerState.scrollToPage(categories.size - 1)
+                }
             }
             LibraryTabs(
                 categories = categories,

+ 2 - 2
app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
@@ -22,7 +23,6 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
 import tachiyomi.domain.library.model.LibraryDisplayMode
 import tachiyomi.domain.library.model.LibraryManga
 import tachiyomi.presentation.core.components.HorizontalPager
-import tachiyomi.presentation.core.components.PagerState
 import tachiyomi.presentation.core.screens.EmptyScreen
 import tachiyomi.presentation.core.util.plus
 
@@ -43,7 +43,7 @@ fun LibraryPager(
     onClickContinueReading: ((LibraryManga) -> Unit)?,
 ) {
     HorizontalPager(
-        count = pageCount,
+        pageCount = pageCount,
         modifier = Modifier.fillMaxSize(),
         state = state,
         verticalAlignment = Alignment.Top,

+ 1 - 1
app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt

@@ -1,6 +1,7 @@
 package eu.kanade.presentation.library.components
 
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.pager.PagerState
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.ScrollableTabRow
 import androidx.compose.material3.Tab
@@ -8,7 +9,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.category.visualName
 import tachiyomi.domain.category.model.Category
-import tachiyomi.presentation.core.components.PagerState
 import tachiyomi.presentation.core.components.material.Divider
 import tachiyomi.presentation.core.components.material.TabIndicator
 import tachiyomi.presentation.core.components.material.TabText

+ 35 - 278
presentation-core/src/main/java/tachiyomi/presentation/core/components/Pager.kt

@@ -1,300 +1,57 @@
 package tachiyomi.presentation.core.components
 
-import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider
-import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.LazyListItemInfo
-import androidx.compose.foundation.lazy.LazyListLayoutInfo
-import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.pager.PageSize
+import androidx.compose.foundation.pager.PagerDefaults
+import androidx.compose.foundation.pager.PagerSnapDistance
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.listSaver
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMaxBy
-import androidx.compose.ui.util.fastSumBy
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlin.math.abs
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 
+/**
+ * Horizontal Pager with custom SnapFlingBehavior for a more natural swipe feeling
+ */
 @Composable
 fun HorizontalPager(
-    count: Int,
+    pageCount: Int,
     modifier: Modifier = Modifier,
     state: PagerState = rememberPagerState(),
-    key: ((page: Int) -> Any)? = null,
-    contentPadding: PaddingValues = PaddingValues(),
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    pageSize: PageSize = PageSize.Fill,
+    beyondBoundsPageCount: Int = 0,
+    pageSpacing: Dp = 0.dp,
     verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
     userScrollEnabled: Boolean = true,
-    content: @Composable BoxScope.(page: Int) -> Unit,
+    reverseLayout: Boolean = false,
+    key: ((index: Int) -> Any)? = null,
+    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
+        Orientation.Horizontal,
+    ),
+    pageContent: @Composable (page: Int) -> Unit,
 ) {
-    Pager(
-        count = count,
+    androidx.compose.foundation.pager.HorizontalPager(
+        pageCount = pageCount,
         modifier = modifier,
         state = state,
-        isVertical = false,
-        key = key,
         contentPadding = contentPadding,
+        pageSize = pageSize,
+        beyondBoundsPageCount = beyondBoundsPageCount,
+        pageSpacing = pageSpacing,
         verticalAlignment = verticalAlignment,
+        flingBehavior = PagerDefaults.flingBehavior(
+            state = state,
+            pagerSnapDistance = PagerSnapDistance.atMost(0),
+        ),
         userScrollEnabled = userScrollEnabled,
-        content = content,
+        reverseLayout = reverseLayout,
+        key = key,
+        pageNestedScrollConnection = pageNestedScrollConnection,
+        pageContent = pageContent,
     )
 }
-
-@Composable
-private fun Pager(
-    count: Int,
-    modifier: Modifier,
-    state: PagerState,
-    isVertical: Boolean,
-    key: ((page: Int) -> Any)?,
-    contentPadding: PaddingValues,
-    userScrollEnabled: Boolean,
-    verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
-    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
-    content: @Composable BoxScope.(page: Int) -> Unit,
-) {
-    LaunchedEffect(count) {
-        state.currentPage = minOf(count - 1, state.currentPage).coerceAtLeast(0)
-    }
-
-    LaunchedEffect(state) {
-        snapshotFlow { state.mostVisiblePageLayoutInfo?.index }
-            .distinctUntilChanged()
-            .collect { state.updateCurrentPageBasedOnLazyListState() }
-    }
-
-    if (isVertical) {
-        LazyColumn(
-            modifier = modifier,
-            state = state.lazyListState,
-            contentPadding = contentPadding,
-            horizontalAlignment = horizontalAlignment,
-            verticalArrangement = Arrangement.aligned(verticalAlignment),
-            userScrollEnabled = userScrollEnabled,
-            flingBehavior = rememberLazyListSnapFlingBehavior(lazyListState = state.lazyListState),
-        ) {
-            items(
-                count = count,
-                key = key,
-            ) { page ->
-                Box(
-                    modifier = Modifier
-                        .fillParentMaxHeight()
-                        .wrapContentSize(),
-                ) {
-                    content(this, page)
-                }
-            }
-        }
-    } else {
-        LazyRow(
-            modifier = modifier,
-            state = state.lazyListState,
-            contentPadding = contentPadding,
-            verticalAlignment = verticalAlignment,
-            horizontalArrangement = Arrangement.aligned(horizontalAlignment),
-            userScrollEnabled = userScrollEnabled,
-            flingBehavior = rememberLazyListSnapFlingBehavior(lazyListState = state.lazyListState),
-        ) {
-            items(
-                count = count,
-                key = key,
-            ) { page ->
-                Box(
-                    modifier = Modifier
-                        .fillParentMaxWidth()
-                        .wrapContentSize(),
-                ) {
-                    content(this, page)
-                }
-            }
-        }
-    }
-}
-
-@Composable
-fun rememberPagerState(
-    initialPage: Int = 0,
-) = rememberSaveable(saver = PagerState.Saver) {
-    PagerState(currentPage = initialPage)
-}
-
-@Stable
-class PagerState(
-    currentPage: Int = 0,
-) {
-    init { check(currentPage >= 0) { "currentPage cannot be less than zero" } }
-
-    val lazyListState = LazyListState(firstVisibleItemIndex = currentPage)
-
-    private val pageSize: Int
-        get() = visiblePages.firstOrNull()?.size ?: 0
-
-    private var _currentPage by mutableStateOf(currentPage)
-
-    private val layoutInfo: LazyListLayoutInfo
-        get() = lazyListState.layoutInfo
-
-    private val visiblePages: List<LazyListItemInfo>
-        get() = layoutInfo.visibleItemsInfo
-
-    var currentPage: Int
-        get() = _currentPage
-        set(value) {
-            if (value != _currentPage) {
-                _currentPage = value
-            }
-        }
-
-    val mostVisiblePageLayoutInfo: LazyListItemInfo?
-        get() {
-            val layoutInfo = lazyListState.layoutInfo
-            return layoutInfo.visibleItemsInfo.fastMaxBy {
-                val start = maxOf(it.offset, 0)
-                val end = minOf(
-                    it.offset + it.size,
-                    layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding,
-                )
-                end - start
-            }
-        }
-
-    private val closestPageToSnappedPosition: LazyListItemInfo?
-        get() = visiblePages.fastMaxBy {
-            -abs(
-                calculateDistanceToDesiredSnapPosition(
-                    layoutInfo,
-                    it,
-                    SnapAlignmentStartToStart,
-                ),
-            )
-        }
-
-    val currentPageOffsetFraction: Float by derivedStateOf {
-        val currentPagePositionOffset = closestPageToSnappedPosition?.offset ?: 0
-        val pageUsedSpace = pageSize.toFloat()
-        if (pageUsedSpace == 0f) {
-            // Default to 0 when there's no info about the page size yet.
-            0f
-        } else {
-            ((-currentPagePositionOffset) / (pageUsedSpace)).coerceIn(
-                MinPageOffset,
-                MaxPageOffset,
-            )
-        }
-    }
-
-    fun updateCurrentPageBasedOnLazyListState() {
-        mostVisiblePageLayoutInfo?.let {
-            currentPage = it.index
-        }
-    }
-
-    suspend fun animateScrollToPage(page: Int) {
-        lazyListState.animateScrollToItem(index = page)
-    }
-
-    suspend fun scrollToPage(page: Int) {
-        lazyListState.scrollToItem(index = page)
-        updateCurrentPageBasedOnLazyListState()
-    }
-
-    companion object {
-        val Saver: Saver<PagerState, *> = listSaver(
-            save = { listOf(it.currentPage) },
-            restore = { PagerState(it[0]) },
-        )
-    }
-}
-
-private const val MinPageOffset = -0.5f
-private const val MaxPageOffset = 0.5f
-internal val SnapAlignmentStartToStart: (layoutSize: Float, itemSize: Float) -> Float =
-    { _, _ -> 0f }
-
-// https://android.googlesource.com/platform/frameworks/support/+/refs/changes/78/2160778/35/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt
-private fun lazyListSnapLayoutInfoProvider(
-    lazyListState: LazyListState,
-    positionInLayout: (layoutSize: Float, itemSize: Float) -> Float = { layoutSize, itemSize ->
-        layoutSize / 2f - itemSize / 2f
-    },
-) = object : SnapLayoutInfoProvider {
-
-    private val layoutInfo: LazyListLayoutInfo
-        get() = lazyListState.layoutInfo
-
-    // Single page snapping is the default
-    override fun Density.calculateApproachOffset(initialVelocity: Float): Float = 0f
-
-    override fun Density.calculateSnappingOffsetBounds(): ClosedFloatingPointRange<Float> {
-        var lowerBoundOffset = Float.NEGATIVE_INFINITY
-        var upperBoundOffset = Float.POSITIVE_INFINITY
-
-        layoutInfo.visibleItemsInfo.fastForEach { item ->
-            val offset =
-                calculateDistanceToDesiredSnapPosition(layoutInfo, item, positionInLayout)
-
-            // Find item that is closest to the center
-            if (offset <= 0 && offset > lowerBoundOffset) {
-                lowerBoundOffset = offset
-            }
-
-            // Find item that is closest to center, but after it
-            if (offset >= 0 && offset < upperBoundOffset) {
-                upperBoundOffset = offset
-            }
-        }
-
-        return lowerBoundOffset.rangeTo(upperBoundOffset)
-    }
-
-    override fun Density.calculateSnapStepSize(): Float = with(layoutInfo) {
-        if (visibleItemsInfo.isNotEmpty()) {
-            visibleItemsInfo.fastSumBy { it.size } / visibleItemsInfo.size.toFloat()
-        } else {
-            0f
-        }
-    }
-}
-
-@Composable
-private fun rememberLazyListSnapFlingBehavior(lazyListState: LazyListState): FlingBehavior {
-    val snappingLayout = remember(lazyListState) { lazyListSnapLayoutInfoProvider(lazyListState) }
-    return rememberSnapFlingBehavior(snappingLayout)
-}
-
-private fun calculateDistanceToDesiredSnapPosition(
-    layoutInfo: LazyListLayoutInfo,
-    item: LazyListItemInfo,
-    positionInLayout: (layoutSize: Float, itemSize: Float) -> Float,
-): Float {
-    val containerSize =
-        with(layoutInfo) { singleAxisViewportSize - beforeContentPadding - afterContentPadding }
-
-    val desiredDistance =
-        positionInLayout(containerSize.toFloat(), item.size.toFloat())
-
-    val itemCurrentPosition = item.offset
-    return itemCurrentPosition - desiredDistance
-}
-
-private val LazyListLayoutInfo.singleAxisViewportSize: Int
-    get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width