Fixes long term issue with the video in the Shorts feed not aligning with the padding of the screen

This commit is contained in:
Vitor Pamplona 2024-10-08 18:31:54 -04:00
parent 86e6d56dcd
commit d320b951ae
3 changed files with 131 additions and 10 deletions

View File

@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.feeds
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.pager.PagerState
@ -115,20 +114,35 @@ fun rememberForeverLazyListState(
return scrollState
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun rememberForeverPagerState(
key: String,
initialFirstVisibleItemIndex: Int = 0,
initialFirstVisibleItemScrollOffset: Float = 0.0f,
pageCount: () -> Int,
): PagerState =
rememberForeverPagerState(key, initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset, pageCount) { initialPage, initialPageOffsetFraction, pageCount ->
rememberPagerState(initialPage, initialPageOffsetFraction, pageCount)
}
@Composable
fun rememberForeverPagerState(
key: String,
initialFirstVisibleItemIndex: Int = 0,
initialFirstVisibleItemScrollOffset: Float = 0.0f,
pageCount: () -> Int,
rememberPagerStateFunction: @Composable (
initialPage: Int,
initialPageOffsetFraction: Float,
pageCount: () -> Int,
) -> PagerState,
): PagerState {
val savedValue = savedScrollStates[key]
val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex
val savedOffset = savedValue?.scrollOffsetFraction ?: initialFirstVisibleItemScrollOffset
val scrollState =
rememberPagerState(
rememberPagerStateFunction(
savedIndex,
savedOffset,
pageCount,

View File

@ -0,0 +1,111 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.video
import androidx.annotation.FloatRange
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState
import kotlin.math.abs
/**
* This file only exists to fix an interference between the Disappearing Top
* and Bottom Scaffold bars and the offsetFraction of the pager. The current
* implementation ends the scroll at a state where some fraction is still present
* which places videos away from their natural position in the page.
*
* The fix simply animates it back to the fraction = 0.
*/
@Composable
fun myRememberPagerState(
initialPage: Int = 0,
@FloatRange(from = -0.5, to = 0.5) initialPageOffsetFraction: Float = 0f,
pageCount: () -> Int,
): PagerState =
rememberSaveable(saver = DefaultPagerState.Saver) {
DefaultPagerState(
initialPage,
initialPageOffsetFraction,
pageCount,
)
}.apply {
pageCountState.value = pageCount
}
@Composable
fun myRememberForeverPagerState(
key: String,
initialFirstVisibleItemIndex: Int = 0,
initialFirstVisibleItemScrollOffset: Float = 0.0f,
pageCount: () -> Int,
): PagerState =
rememberForeverPagerState(key, initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset, pageCount) { initialPage, initialPageOffsetFraction, pageCount ->
myRememberPagerState(initialPage, initialPageOffsetFraction, pageCount)
}
private class DefaultPagerState(
currentPage: Int,
currentPageOffsetFraction: Float,
updatedPageCount: () -> Int,
) : PagerState(currentPage, currentPageOffsetFraction) {
var pageCountState = mutableStateOf(updatedPageCount)
override val pageCount: Int get() = pageCountState.value.invoke()
override suspend fun scroll(
scrollPriority: MutatePriority,
block: suspend ScrollScope.() -> Unit,
) {
super.scroll(scrollPriority, block)
if (abs(currentPageOffsetFraction) > 0) {
animateScrollToPage(currentPage, 0f)
}
}
companion object {
/**
* To keep current page and current page offset saved
*/
val Saver: Saver<DefaultPagerState, *> =
listSaver(
save = {
listOf(
it.currentPage,
(it.currentPageOffsetFraction).coerceIn(-0.5f, 0.5f),
it.pageCount,
)
},
restore = {
DefaultPagerState(
currentPage = it[0] as Int,
currentPageOffsetFraction = it[1] as Float,
updatedPageCount = { it[2] as Int },
)
},
)
}
}

View File

@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.video
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -33,7 +32,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Icon
@ -47,11 +45,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType
@ -69,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop
import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.Route
@ -236,7 +233,6 @@ private fun LoadedState(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SlidingCarousel(
loaded: FeedState.Loaded,
@ -249,9 +245,9 @@ fun SlidingCarousel(
val pagerState =
if (pagerStateKey != null) {
rememberForeverPagerState(pagerStateKey, items.list.size) { items.list.size }
myRememberForeverPagerState(pagerStateKey, items.list.size) { items.list.size }
} else {
rememberPagerState(items.list.size) { items.list.size }
myRememberPagerState(items.list.size) { items.list.size }
}
WatchScrollToTop(videoFeedContentState, pagerState)