New Pager implementation (#8323)
Minimal implementation using new Compose SnapFlingBehavior
This commit is contained in:
parent
5b12c144da
commit
f9c25b350e
7 changed files with 186 additions and 10 deletions
|
@ -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)
|
||||||
|
|
182
app/src/main/java/eu/kanade/presentation/components/Pager.kt
Normal file
182
app/src/main/java/eu/kanade/presentation/components/Pager.kt
Normal 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]) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
Reference in a new issue