New Pager implementation (#8323)

Minimal implementation using new Compose SnapFlingBehavior
This commit is contained in:
Ivan Iskandar 2022-10-29 23:32:55 +07:00 committed by GitHub
parent 5b12c144da
commit f9c25b350e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 186 additions and 10 deletions

View file

@ -176,8 +176,6 @@ dependencies {
implementation(compose.accompanist.webview) implementation(compose.accompanist.webview)
implementation(compose.accompanist.swiperefresh) implementation(compose.accompanist.swiperefresh)
implementation(compose.accompanist.flowlayout) implementation(compose.accompanist.flowlayout)
implementation(compose.accompanist.pager.core)
implementation(compose.accompanist.pager.indicators)
implementation(compose.accompanist.permissions) implementation(compose.accompanist.permissions)
implementation(androidx.paging.runtime) implementation(androidx.paging.runtime)

View file

@ -0,0 +1,182 @@
package eu.kanade.presentation.components
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.LazyListState
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.getValue
import androidx.compose.runtime.mutableStateOf
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.util.fastMaxBy
import kotlinx.coroutines.flow.distinctUntilChanged
@Composable
fun HorizontalPager(
count: Int,
modifier: Modifier = Modifier,
state: PagerState = rememberPagerState(),
key: ((page: Int) -> Any)? = null,
contentPadding: PaddingValues = PaddingValues(),
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
userScrollEnabled: Boolean = true,
content: @Composable BoxScope.(page: Int) -> Unit,
) {
Pager(
count = count,
modifier = modifier,
state = state,
isVertical = false,
key = key,
contentPadding = contentPadding,
verticalAlignment = verticalAlignment,
userScrollEnabled = userScrollEnabled,
content = content,
)
}
@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 = rememberSnapFlingBehavior(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 = rememberSnapFlingBehavior(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 var _currentPage by mutableStateOf(currentPage)
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
}
}
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]) },
)
}
}

View file

@ -16,8 +16,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -15,12 +15,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import com.google.accompanist.pager.rememberPagerState
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.presentation.components.SwipeRefresh import eu.kanade.presentation.components.SwipeRefresh
import eu.kanade.presentation.components.rememberPagerState
import eu.kanade.presentation.library.LibraryState import eu.kanade.presentation.library.LibraryState
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import kotlinx.coroutines.delay import kotlinx.coroutines.delay

View file

@ -10,11 +10,11 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.presentation.components.HorizontalPager
import eu.kanade.presentation.components.PagerState
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
@Composable @Composable

View file

@ -10,11 +10,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.pager.PagerState
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.presentation.category.visualName import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.AppStateBanners
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.PagerState
import eu.kanade.presentation.components.TabIndicator import eu.kanade.presentation.components.TabIndicator
import eu.kanade.presentation.components.TabText import eu.kanade.presentation.components.TabText
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -19,6 +19,4 @@ material-icons = { module = "androidx.compose.material:material-icons-extended"
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" } accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version.ref = "accompanist" }
accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" } accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" } accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-pager-core = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanist" }
accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }