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 package com.vitorpamplona.amethyst.ui.feeds
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
@@ -115,20 +114,35 @@ fun rememberForeverLazyListState(
return scrollState return scrollState
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun rememberForeverPagerState( fun rememberForeverPagerState(
key: String, key: String,
initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemIndex: Int = 0,
initialFirstVisibleItemScrollOffset: Float = 0.0f, initialFirstVisibleItemScrollOffset: Float = 0.0f,
pageCount: () -> Int, 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 { ): PagerState {
val savedValue = savedScrollStates[key] val savedValue = savedScrollStates[key]
val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex
val savedOffset = savedValue?.scrollOffsetFraction ?: initialFirstVisibleItemScrollOffset val savedOffset = savedValue?.scrollOffsetFraction ?: initialFirstVisibleItemScrollOffset
val scrollState = val scrollState =
rememberPagerState( rememberPagerStateFunction(
savedIndex, savedIndex,
savedOffset, savedOffset,
pageCount, 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 package com.vitorpamplona.amethyst.ui.screen.loggedIn.video
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -47,11 +45,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
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.LocalLifecycleOwner
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.FeatureSetType 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.RefresheableBox
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop 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.AppBottomBar
import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.navigation.Route
@@ -236,7 +233,6 @@ private fun LoadedState(
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun SlidingCarousel( fun SlidingCarousel(
loaded: FeedState.Loaded, loaded: FeedState.Loaded,
@@ -249,9 +245,9 @@ fun SlidingCarousel(
val pagerState = val pagerState =
if (pagerStateKey != null) { if (pagerStateKey != null) {
rememberForeverPagerState(pagerStateKey, items.list.size) { items.list.size } myRememberForeverPagerState(pagerStateKey, items.list.size) { items.list.size }
} else { } else {
rememberPagerState(items.list.size) { items.list.size } myRememberPagerState(items.list.size) { items.list.size }
} }
WatchScrollToTop(videoFeedContentState, pagerState) WatchScrollToTop(videoFeedContentState, pagerState)