Adjust tab indicator visual (#9219)

Now behaves like the non-compose indicator by showing the swipe progress too
This commit is contained in:
Ivan Iskandar 2023-03-17 09:20:25 +07:00 committed by GitHub
parent 4d3e13b0d1
commit 18e55aa25f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 82 additions and 10 deletions

View file

@ -56,7 +56,7 @@ fun TabbedDialog(
TabRow(
modifier = Modifier.weight(1f),
selectedTabIndex = pagerState.currentPage,
indicator = { TabIndicator(it[pagerState.currentPage]) },
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
divider = {},
) {
tabTitles.fastForEachIndexed { i, tab ->

View file

@ -69,7 +69,7 @@ fun TabbedScreen(
) {
TabRow(
selectedTabIndex = state.currentPage,
indicator = { TabIndicator(it[state.currentPage]) },
indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) },
) {
tabs.forEachIndexed { index, tab ->
Tab(

View file

@ -65,7 +65,7 @@ fun LibraryContent(
}
LibraryTabs(
categories = categories,
currentPageIndex = pagerState.currentPage,
pagerState = pagerState,
getNumberOfMangaForCategory = getNumberOfMangaForCategory,
) { scope.launch { pagerState.animateScrollToPage(it) } }
}

View file

@ -8,6 +8,7 @@ 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
@ -15,22 +16,22 @@ import tachiyomi.presentation.core.components.material.TabText
@Composable
internal fun LibraryTabs(
categories: List<Category>,
currentPageIndex: Int,
pagerState: PagerState,
getNumberOfMangaForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit,
) {
Column {
ScrollableTabRow(
selectedTabIndex = currentPageIndex,
selectedTabIndex = pagerState.currentPage,
edgePadding = 0.dp,
indicator = { TabIndicator(it[currentPageIndex]) },
indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) },
// TODO: use default when width is fixed upstream
// https://issuetracker.google.com/issues/242879624
divider = {},
) {
categories.forEachIndexed { index, category ->
Tab(
selected = currentPageIndex == index,
selected = pagerState.currentPage == index,
onClick = { onTabItemClick(index) },
text = {
TabText(

View file

@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.LazyRow
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
@ -31,6 +32,7 @@ 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
@Composable
fun HorizontalPager(
@ -143,8 +145,17 @@ class PagerState(
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) {
@ -166,6 +177,31 @@ class PagerState(
}
}
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
@ -189,6 +225,11 @@ class PagerState(
}
}
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,

View file

@ -1,27 +1,57 @@
package tachiyomi.presentation.core.components.material
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TabPosition
import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import tachiyomi.presentation.core.components.Pill
private fun Modifier.tabIndicatorOffset(
currentTabPosition: TabPosition,
currentPageOffsetFraction: Float,
) = composed {
val currentTabWidth by animateDpAsState(
targetValue = currentTabPosition.width,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
)
val offset by animateDpAsState(
targetValue = currentTabPosition.left + (currentTabWidth * currentPageOffsetFraction),
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
)
fillMaxWidth()
.wrapContentSize(Alignment.BottomStart)
.offset { IntOffset(x = offset.roundToPx(), y = 0) }
.width(currentTabWidth)
}
@Composable
fun TabIndicator(currentTabPosition: TabPosition) {
fun TabIndicator(
currentTabPosition: TabPosition,
currentPageOffsetFraction: Float,
) {
TabRowDefaults.Indicator(
Modifier
.tabIndicatorOffset(currentTabPosition)
.tabIndicatorOffset(currentTabPosition, currentPageOffsetFraction)
.padding(horizontal = 8.dp)
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)),
)