From f9c25b350ed1eb5cb418e804a829f40ecfb0af2e Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 29 Oct 2022 23:32:55 +0700 Subject: [PATCH] New Pager implementation (#8323) Minimal implementation using new Compose SnapFlingBehavior --- app/build.gradle.kts | 2 - .../kanade/presentation/components/Pager.kt | 182 ++++++++++++++++++ .../presentation/components/TabbedScreen.kt | 2 - .../library/components/LibraryContent.kt | 2 +- .../library/components/LibraryPager.kt | 4 +- .../library/components/LibraryTabs.kt | 2 +- gradle/compose.versions.toml | 2 - 7 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/components/Pager.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 13e5580e4..28c1aed82 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -176,8 +176,6 @@ dependencies { implementation(compose.accompanist.webview) implementation(compose.accompanist.swiperefresh) implementation(compose.accompanist.flowlayout) - implementation(compose.accompanist.pager.core) - implementation(compose.accompanist.pager.indicators) implementation(compose.accompanist.permissions) implementation(androidx.paging.runtime) diff --git a/app/src/main/java/eu/kanade/presentation/components/Pager.kt b/app/src/main/java/eu/kanade/presentation/components/Pager.kt new file mode 100644 index 000000000..285abac68 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/Pager.kt @@ -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 = listSaver( + save = { listOf(it.currentPage) }, + restore = { PagerState(it[0]) }, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt index 4f747c7f2..e7354d11e 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt @@ -16,8 +16,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection 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 kotlinx.coroutines.launch diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt index d03aa8505..134fa5936 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt @@ -15,12 +15,12 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection -import com.google.accompanist.pager.rememberPagerState import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.domain.category.model.Category import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryManga import eu.kanade.presentation.components.SwipeRefresh +import eu.kanade.presentation.components.rememberPagerState import eu.kanade.presentation.library.LibraryState import eu.kanade.tachiyomi.ui.library.LibraryItem import kotlinx.coroutines.delay diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt index bbf7ff9bd..292ca462a 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryPager.kt @@ -10,11 +10,11 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier 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.domain.library.model.LibraryDisplayMode 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 @Composable diff --git a/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt b/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt index 9b54b2587..621f29e95 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/LibraryTabs.kt @@ -10,11 +10,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.unit.dp -import com.google.accompanist.pager.PagerState import eu.kanade.domain.category.model.Category import eu.kanade.presentation.category.visualName import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.Divider +import eu.kanade.presentation.components.PagerState import eu.kanade.presentation.components.TabIndicator import eu.kanade.presentation.components.TabText import kotlinx.coroutines.launch diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 391a0f5f7..dcd4d02be 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -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-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", 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" }