diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e8af3905..fe9efa4f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -103,8 +105,10 @@ ksp { } composeCompiler { - enableStrongSkippingMode = true - enableNonSkippingGroupOptimization = true + featureFlags.addAll( + ComposeFeatureFlag.StrongSkipping, + ComposeFeatureFlag.OptimizeNonSkippingGroups + ) if (project.findProperty("enableComposeCompilerReports") == "true") { val dest = layout.buildDirectory.dir("compose_metrics") diff --git a/app/schemas/app.banafsh.android.DatabaseInitializer/1.json b/app/schemas/app.banafsh.android.DatabaseInitializer/1.json index 58d0108f..c42da254 100644 --- a/app/schemas/app.banafsh.android.DatabaseInitializer/1.json +++ b/app/schemas/app.banafsh.android.DatabaseInitializer/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "fee48b09836f856f36971192954afe4e", + "identityHash": "9e031eabcd85865fe7bfd89f6fbd7a8e", "entities": [ { "tableName": "Song", @@ -505,7 +505,7 @@ }, { "tableName": "QueuedSong", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `position` INTEGER, `id` TEXT NOT NULL, `title` TEXT NOT NULL, `artistsText` TEXT, `durationText` TEXT, `thumbnailUrl` TEXT, `dateModified` INTEGER, `likedAt` INTEGER, `totalPlayTimeMs` INTEGER NOT NULL, `loudnessBoost` REAL, `blacklisted` INTEGER NOT NULL DEFAULT false, `explicit` INTEGER NOT NULL DEFAULT false, `path` TEXT)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `songId` TEXT NOT NULL, `position` INTEGER)", "fields": [ { "fieldPath": "itemId", @@ -514,83 +514,15 @@ "notNull": true }, { - "fieldPath": "position", - "columnName": "position", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "song.id", - "columnName": "id", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "song.title", - "columnName": "title", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "song.artistsText", - "columnName": "artistsText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "song.durationText", - "columnName": "durationText", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "song.thumbnailUrl", - "columnName": "thumbnailUrl", + "fieldPath": "songId", + "columnName": "songId", "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "song.dateModified", - "columnName": "dateModified", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "song.likedAt", - "columnName": "likedAt", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "song.totalPlayTimeMs", - "columnName": "totalPlayTimeMs", - "affinity": "INTEGER", "notNull": true }, { - "fieldPath": "song.loudnessBoost", - "columnName": "loudnessBoost", - "affinity": "REAL", - "notNull": false - }, - { - "fieldPath": "song.blacklisted", - "columnName": "blacklisted", - "affinity": "INTEGER", - "notNull": true, - "defaultValue": "false" - }, - { - "fieldPath": "song.explicit", - "columnName": "explicit", + "fieldPath": "position", + "columnName": "position", "affinity": "INTEGER", - "notNull": true, - "defaultValue": "false" - }, - { - "fieldPath": "song.path", - "columnName": "path", - "affinity": "TEXT", "notNull": false } ], @@ -839,7 +771,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fee48b09836f856f36971192954afe4e')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9e031eabcd85865fe7bfd89f6fbd7a8e')" ] } } \ No newline at end of file diff --git a/app/src/main/kotlin/app/banafsh/android/MainApplication.kt b/app/src/main/kotlin/app/banafsh/android/MainApplication.kt index 72e3a502..0f4f87b5 100644 --- a/app/src/main/kotlin/app/banafsh/android/MainApplication.kt +++ b/app/src/main/kotlin/app/banafsh/android/MainApplication.kt @@ -13,7 +13,6 @@ import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState -import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -31,8 +30,6 @@ import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars -import androidx.compose.material.ripple.LocalRippleTheme -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState @@ -64,6 +61,7 @@ import app.banafsh.android.lib.compose.persist.PersistMap import app.banafsh.android.lib.compose.preferences.PreferencesHolder import app.banafsh.android.lib.core.ui.Dimensions import app.banafsh.android.lib.core.ui.LocalAppearance +import app.banafsh.android.lib.core.ui.LocalRippleTheme import app.banafsh.android.lib.core.ui.SystemBarAppearance import app.banafsh.android.lib.core.ui.appearance import app.banafsh.android.lib.core.ui.rippleTheme @@ -235,7 +233,6 @@ class MainActivity : ComponentActivity(), MonetColorsChangedListener { } CompositionLocalProvider( - LocalIndication provides rememberRipple(), LocalRippleTheme provides rippleTheme(), LocalShimmerTheme provides shimmerTheme(), LocalPlayerServiceBinder provides vm.binder, diff --git a/app/src/main/kotlin/app/banafsh/android/lib/core/ui/Ripple.kt b/app/src/main/kotlin/app/banafsh/android/lib/core/ui/Ripple.kt index fa0fdfbc..e5d06ade 100644 --- a/app/src/main/kotlin/app/banafsh/android/lib/core/ui/Ripple.kt +++ b/app/src/main/kotlin/app/banafsh/android/lib/core/ui/Ripple.kt @@ -1,10 +1,13 @@ package app.banafsh.android.lib.core.ui -import androidx.compose.material.ripple.RippleAlpha -import androidx.compose.material.ripple.RippleTheme +import androidx.compose.foundation.interaction.Interaction import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance @Composable fun rippleTheme(appearance: Appearance = LocalAppearance.current) = remember( @@ -25,3 +28,184 @@ fun rippleTheme(appearance: Appearance = LocalAppearance.current) = remember( ) } } + +interface RippleTheme { + /** + * @return the default ripple color at the call site's position in the hierarchy. + * This color will be used when a color is not explicitly set in the ripple itself. + * @see defaultRippleColor + */ + @Composable + fun defaultColor(): Color + + /** + * @return the [RippleAlpha] used to calculate the alpha for the ripple depending on the + * [Interaction] for a given component. This will be set as the alpha channel for + * [defaultColor] or the color explicitly provided to the ripple. + * @see defaultRippleAlpha + */ + @Composable + fun rippleAlpha(): RippleAlpha + + companion object { + /** + * Represents the default color that will be used for a ripple if a color has not been + * explicitly set on the ripple instance. + * + * @param contentColor the color of content (text or iconography) in the component that + * contains the ripple. + * @param lightTheme whether the theme is light or not + */ + fun defaultRippleColor( + contentColor: Color, + lightTheme: Boolean + ): Color { + val contentLuminance = contentColor.luminance() + // If we are on a colored surface (typically indicated by low luminance content), the + // ripple color should be white. + return if (!lightTheme && contentLuminance < 0.5) { + Color.White + // Otherwise use contentColor + } else { + contentColor + } + } + + /** + * Represents the default [RippleAlpha] that will be used for a ripple to indicate different + * states. + * + * @param contentColor the color of content (text or iconography) in the component that + * contains the ripple. + * @param lightTheme whether the theme is light or not + */ + fun defaultRippleAlpha(contentColor: Color, lightTheme: Boolean): RippleAlpha { + return when { + lightTheme -> { + if (contentColor.luminance() > 0.5) { + LightThemeHighContrastRippleAlpha + } else { + LightThemeLowContrastRippleAlpha + } + } + + else -> { + DarkThemeRippleAlpha + } + } + } + } +} + +/** + * RippleAlpha defines the alpha of the ripple / state layer for different [Interaction]s. + * + * On Android, because the press ripple is drawn using the framework's RippleDrawable, there are + * constraints / different behaviours for the actual press alpha used on different API versions. + * Note that this *only* affects [pressedAlpha] - the other values are guaranteed to be consistent, + * as they do not rely on framework code. Specifically: + * + * API 21-27: The actual ripple is split into two 'layers', with the alpha applied to both layers, + * so there is no uniform 'alpha'. + * API 28-32: The ripple is just one layer, but the alpha is clamped to a maximum of 0.5f - it is + * not possible to have a fully opaque ripple. + * API 33: There is a bug where the ripple is clamped to a *minimum* of 0.5, instead of a maximum + * like before - this should be resolved in future versions. + * + * @property draggedAlpha the alpha used when the ripple is dragged + * @property focusedAlpha the alpha used when the ripple is focused + * @property hoveredAlpha the alpha used when the ripple is hovered + * @property pressedAlpha the alpha used when the ripple is pressed + */ +@Immutable +class RippleAlpha( + val draggedAlpha: Float, + val focusedAlpha: Float, + val hoveredAlpha: Float, + val pressedAlpha: Float +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RippleAlpha) return false + + if (draggedAlpha != other.draggedAlpha) return false + if (focusedAlpha != other.focusedAlpha) return false + if (hoveredAlpha != other.hoveredAlpha) return false + if (pressedAlpha != other.pressedAlpha) return false + + return true + } + + override fun hashCode(): Int { + var result = draggedAlpha.hashCode() + result = 31 * result + focusedAlpha.hashCode() + result = 31 * result + hoveredAlpha.hashCode() + result = 31 * result + pressedAlpha.hashCode() + return result + } + + override fun toString(): String = "RippleAlpha(draggedAlpha=$draggedAlpha, focusedAlpha=$focusedAlpha, " + + "hoveredAlpha=$hoveredAlpha, pressedAlpha=$pressedAlpha)" +} + +val LocalRippleTheme: ProvidableCompositionLocal = + staticCompositionLocalOf { DebugRippleTheme } + +/** + * Alpha values for high luminance content in a light theme. + * + * This content will typically be placed on colored surfaces, so it is important that the + * contrast here is higher to meet accessibility standards, and increase legibility. + * + * These levels are typically used for text / iconography in primary colored tabs / + * bottom navigation / etc. + */ +private val LightThemeHighContrastRippleAlpha = RippleAlpha( + pressedAlpha = 0.24f, + focusedAlpha = 0.24f, + draggedAlpha = 0.16f, + hoveredAlpha = 0.08f +) + +/** + * Alpha levels for low luminance content in a light theme. + * + * This content will typically be placed on grayscale surfaces, so the contrast here can be lower + * without sacrificing accessibility and legibility. + * + * These levels are typically used for body text on the main surface (white in light theme, grey + * in dark theme) and text / iconography in surface colored tabs / bottom navigation / etc. + */ +private val LightThemeLowContrastRippleAlpha = RippleAlpha( + pressedAlpha = 0.12f, + focusedAlpha = 0.12f, + draggedAlpha = 0.08f, + hoveredAlpha = 0.04f +) + +/** + * Alpha levels for all content in a dark theme. + */ +private val DarkThemeRippleAlpha = RippleAlpha( + pressedAlpha = 0.10f, + focusedAlpha = 0.12f, + draggedAlpha = 0.08f, + hoveredAlpha = 0.04f +) + +/** + * Simple debug indication that will assume black content color and light theme. You should + * instead provide your own theme with meaningful values - this exists as an alternative to + * crashing if no theme is provided. + */ +@Immutable +private object DebugRippleTheme : RippleTheme { + @Composable + override fun defaultColor() = RippleTheme.defaultRippleColor(Color.Black, lightTheme = true) + + @Composable + override fun rippleAlpha(): RippleAlpha = RippleTheme.defaultRippleAlpha( + Color.Black, + lightTheme = true + ) +} diff --git a/app/src/main/kotlin/app/banafsh/android/models/QueuedSong.kt b/app/src/main/kotlin/app/banafsh/android/models/QueuedSong.kt index ba8e64e3..dc1a4ceb 100644 --- a/app/src/main/kotlin/app/banafsh/android/models/QueuedSong.kt +++ b/app/src/main/kotlin/app/banafsh/android/models/QueuedSong.kt @@ -1,7 +1,6 @@ package app.banafsh.android.models import androidx.compose.runtime.Immutable -import androidx.room.Embedded import androidx.room.Entity import androidx.room.PrimaryKey @@ -9,6 +8,6 @@ import androidx.room.PrimaryKey @Entity class QueuedSong( @PrimaryKey(autoGenerate = true) val itemId: Long = 0, - @Embedded val song: Song, + val songId: String, var position: Long? ) diff --git a/app/src/main/kotlin/app/banafsh/android/service/PlayerService.kt b/app/src/main/kotlin/app/banafsh/android/service/PlayerService.kt index 246fa682..716f55e4 100644 --- a/app/src/main/kotlin/app/banafsh/android/service/PlayerService.kt +++ b/app/src/main/kotlin/app/banafsh/android/service/PlayerService.kt @@ -114,7 +114,6 @@ import app.banafsh.android.utils.shouldBePlaying import app.banafsh.android.utils.songBundle import app.banafsh.android.utils.thumbnail import app.banafsh.android.utils.timer -import app.banafsh.android.utils.toSong import app.banafsh.android.utils.toast import kotlin.math.roundToInt import kotlin.system.exitProcess @@ -576,7 +575,7 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene Database.insert( mediaItems.mapIndexed { index, mediaItem -> QueuedSong( - song = mediaItem.toSong(), + songId = mediaItem.mediaId, position = if (index == mediaItemIndex) mediaItemPosition else null ) } @@ -599,8 +598,10 @@ class PlayerService : InvincibleService(), Player.Listener, PlaybackStatsListene handler.post { runCatching { player.setMediaItems( - /* mediaItems = */ queue.map { item -> - item.song.asMediaItem + /* mediaItems = */ Database.songs( + queue.map { it.songId } + ).map { + it.asMediaItem .apply { mediaMetadata.extras?.songBundle?.apply { isFromPersistentQueue = true diff --git a/app/src/main/kotlin/app/banafsh/android/ui/components/BottomSheet.kt b/app/src/main/kotlin/app/banafsh/android/ui/components/BottomSheet.kt index aadb4bc3..f5b12d93 100644 --- a/app/src/main/kotlin/app/banafsh/android/ui/components/BottomSheet.kt +++ b/app/src/main/kotlin/app/banafsh/android/ui/components/BottomSheet.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.Indication -import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.DraggableState import androidx.compose.foundation.gestures.detectVerticalDragGestures @@ -19,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf @@ -50,7 +50,7 @@ fun BottomSheet( collapsedContent: @Composable BoxScope.(Modifier) -> Unit, modifier: Modifier = Modifier, onDismiss: (() -> Unit)? = null, - indication: Indication? = LocalIndication.current, + indication: Indication? = ripple(), content: @Composable BoxScope.() -> Unit ) = Box( modifier = modifier @@ -189,7 +189,7 @@ class BottomSheetState internal constructor( override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (expanded && available.y < 0) isTopReached = false - return if (isTopReached && available.y < 0 && source == NestedScrollSource.Drag) { + return if (isTopReached && available.y < 0 && source == NestedScrollSource.UserInput) { dispatchRawDelta(available.y) available } else Offset.Zero @@ -202,7 +202,7 @@ class BottomSheetState internal constructor( ): Offset { if (!isTopReached) isTopReached = consumed.y == 0f && available.y > 0 - return if (isTopReached && source == NestedScrollSource.Drag) { + return if (isTopReached && source == NestedScrollSource.UserInput) { dispatchRawDelta(available.y) available } else Offset.Zero diff --git a/app/src/main/kotlin/app/banafsh/android/ui/components/themed/IconButton.kt b/app/src/main/kotlin/app/banafsh/android/ui/components/themed/IconButton.kt index da2cd162..8cc0ab3e 100644 --- a/app/src/main/kotlin/app/banafsh/android/ui/components/themed/IconButton.kt +++ b/app/src/main/kotlin/app/banafsh/android/ui/components/themed/IconButton.kt @@ -7,7 +7,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -23,7 +23,7 @@ fun HeaderIconButton( @DrawableRes icon: Int, modifier: Modifier = Modifier, enabled: Boolean = true, - indication: Indication? = rememberRipple(bounded = false) + indication: Indication? = ripple(bounded = false) ) { val (colorPalette) = LocalAppearance.current @@ -44,7 +44,7 @@ fun HeaderIconButton( color: Color, modifier: Modifier = Modifier, enabled: Boolean = true, - indication: Indication? = rememberRipple(bounded = false) + indication: Indication? = ripple(bounded = false) ) = IconButton( icon = icon, color = color, @@ -62,7 +62,7 @@ fun IconButton( @DrawableRes icon: Int, modifier: Modifier = Modifier, enabled: Boolean = true, - indication: Indication? = rememberRipple(bounded = false) + indication: Indication? = ripple(bounded = false) ) { val (colorPalette) = LocalAppearance.current @@ -83,7 +83,7 @@ fun IconButton( color: Color, modifier: Modifier = Modifier, enabled: Boolean = true, - indication: Indication? = rememberRipple(bounded = false) + indication: Indication? = ripple(bounded = false) ) = Image( painter = painterResource(icon), contentDescription = null, diff --git a/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Lyrics.kt b/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Lyrics.kt index e5e86989..74d21d64 100644 --- a/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Lyrics.kt +++ b/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Lyrics.kt @@ -31,7 +31,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicText import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -501,7 +501,7 @@ fun Lyrics( modifier = Modifier .padding(all = 4.dp) .clickable( - indication = rememberRipple(bounded = false), + indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, onClick = { onOpenDialog() @@ -519,7 +519,7 @@ fun Lyrics( modifier = Modifier .padding(all = 4.dp) .clickable( - indication = rememberRipple(bounded = false), + indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, onClick = { onMenuLaunch() diff --git a/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Player.kt b/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Player.kt index 9d8dfbaa..e6550c0c 100644 --- a/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Player.kt +++ b/app/src/main/kotlin/app/banafsh/android/ui/screens/player/Player.kt @@ -29,7 +29,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -271,7 +271,7 @@ fun Player( binder.player.play() } }, - indication = rememberRipple(bounded = false), + indication = ripple(bounded = false), interactionSource = remember { MutableInteractionSource() } ) .clip(CircleShape) diff --git a/app/src/main/kotlin/app/banafsh/android/ui/screens/search/OnlineSearch.kt b/app/src/main/kotlin/app/banafsh/android/ui/screens/search/OnlineSearch.kt index 2815a020..2134ad6a 100644 --- a/app/src/main/kotlin/app/banafsh/android/ui/screens/search/OnlineSearch.kt +++ b/app/src/main/kotlin/app/banafsh/android/ui/screens/search/OnlineSearch.kt @@ -20,7 +20,7 @@ import androidx.compose.foundation.text.BasicText import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -108,7 +108,7 @@ fun OnlineSearch( else null } - val rippleIndication = rememberRipple(bounded = false) + val rippleIndication = ripple(bounded = false) val timeIconPainter = painterResource(R.drawable.time) val closeIconPainter = painterResource(R.drawable.close) val arrowForwardIconPainter = painterResource(R.drawable.arrow_forward) diff --git a/app/src/main/kotlin/app/banafsh/android/utils/SnapLayoutInfoProvider.kt b/app/src/main/kotlin/app/banafsh/android/utils/SnapLayoutInfoProvider.kt index a2afb721..5dc4346e 100644 --- a/app/src/main/kotlin/app/banafsh/android/utils/SnapLayoutInfoProvider.kt +++ b/app/src/main/kotlin/app/banafsh/android/utils/SnapLayoutInfoProvider.kt @@ -16,7 +16,6 @@ private val LazyGridLayoutInfo.singleAxisViewportSize: Int get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width context(Density) -@OptIn(ExperimentalFoundationApi::class) private fun SnapLayoutInfoProvider( lazyGridState: LazyGridState, positionInLayout: Density.(layoutSize: Float, itemSize: Float) -> Float = @@ -26,10 +25,10 @@ private fun SnapLayoutInfoProvider( get() = lazyGridState.layoutInfo // Single page snapping is the default - override fun calculateApproachOffset(initialVelocity: Float) = 0f + override fun calculateApproachOffset(velocity: Float, decayOffset: Float): Float = 0f // ignoring the velocity for now since there is no animation spec in this provider - override fun calculateSnappingOffset(currentVelocity: Float): Float { + override fun calculateSnapOffset(velocity: Float): Float { var lowerBoundOffset = Float.NEGATIVE_INFINITY var upperBoundOffset = Float.POSITIVE_INFINITY