From 96f25dd82f57ff4e53dbce5990d7342bd2e975e6 Mon Sep 17 00:00:00 2001 From: Syer10 Date: Sat, 4 Oct 2025 20:27:17 -0400 Subject: [PATCH] Update some dependencies --- android/build.gradle.kts | 4 + build.gradle.kts | 6 +- buildSrc/src/main/kotlin/Config.kt | 7 +- buildSrc/src/main/kotlin/TachideskTasks.kt | 9 +- data/build.gradle.kts | 6 + desktop/build.gradle.kts | 9 + gradle/libs.versions.toml | 24 +- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- presentation/build.gradle.kts | 10 + .../gosyer/jui/ui/base/navigation/Toolbar.kt | 4 +- .../components/DownloadsScreenContent.kt | 2 +- .../components/ExtensionsScreenContent.kt | 4 +- .../components/AndroidBottomActionMenu.kt | 4 +- .../uicore/components/DropdownIconButton.kt | 4 +- .../ca/gosyer/jui/uicore/pager/Pager.kt | 207 +++++++++++------- 16 files changed, 192 insertions(+), 110 deletions(-) diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 737cb9bf..59f0e49d 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -140,3 +140,7 @@ kotlin { languageVersion.set(JavaLanguageVersion.of(Config.androidJvmTarget.target)) } } + +configurations.all { + exclude(group = "org.jetbrains.runtime", module = "jbr-api") +} diff --git a/build.gradle.kts b/build.gradle.kts index 81e2264f..e78e9c0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import Config.migrationCode import Config.serverCode import Config.tachideskVersion import com.codingfeline.buildkonfig.compiler.FieldSpec.Type +import com.google.devtools.ksp.gradle.KspAATask import org.jetbrains.compose.ComposeExtension import org.jetbrains.compose.ComposePlugin import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -77,7 +78,7 @@ subprojects { } plugins.withType { configure { - compileSdkVersion(34) + compileSdkVersion(36) defaultConfig { minSdk = 21 targetSdk = 31 @@ -103,6 +104,9 @@ subprojects { } } plugins.withType { + tasks.withType { + mustRunAfter("generateBuildKonfig") + } configure { defaultConfigs { buildConfigField(Type.STRING, "NAME", rootProject.name, const = true) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index a05bc862..58645a89 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -4,11 +4,10 @@ object Config { const val migrationCode = 5 // Suwayomi-Server version - const val tachideskVersion = "v1.0.0" + const val tachideskVersion = "v2.1.1959" // Match this to the Suwayomi-Server commit count - const val serverCode = 1498 - const val preview = false - const val previewCommit = "54df9d634a1e83143a6cacf6206b6504721b6ca8" + const val serverCode = 1959 + const val preview = true val desktopJvmTarget = JvmTarget.JVM_17 val androidJvmTarget = JvmTarget.JVM_17 diff --git a/buildSrc/src/main/kotlin/TachideskTasks.kt b/buildSrc/src/main/kotlin/TachideskTasks.kt index 6e6d4092..51bed6ad 100644 --- a/buildSrc/src/main/kotlin/TachideskTasks.kt +++ b/buildSrc/src/main/kotlin/TachideskTasks.kt @@ -1,5 +1,4 @@ import Config.preview -import Config.previewCommit import Config.serverCode import Config.tachideskVersion import de.undercouch.gradle.tasks.download.Download @@ -66,7 +65,7 @@ private fun isSigning(properties: Map) = properties["compose.deskt private const val tmpPath = "tmp" private val apiUrl = if (preview) { - "https://api.github.com/repos/Suwayomi/Suwayomi-Server-preview/releases/tags/$previewCommit" + "https://api.github.com/repos/Suwayomi/Suwayomi-Server-preview/releases/tags/$tachideskVersion" } else { "https://api.github.com/repos/Suwayomi/Suwayomi-Server/releases/tags/$tachideskVersion" } @@ -140,8 +139,8 @@ fun TaskContainerScope.registerTachideskTasks(project: Project) { .forEach { val tmpFile = macJarFolder / it.name it.copyTo(tmpFile) - exec { - commandLine( + Runtime.getRuntime().exec( + arrayOf( "/usr/bin/codesign", "-vvvv", "--timestamp", @@ -151,7 +150,7 @@ fun TaskContainerScope.registerTachideskTasks(project: Project) { "--sign", "Developer ID Application: ${getSigningIdentity()}", tmpFile.absolutePathString(), ) - } + ) tmpFile.copyTo(it, overwrite = true) tmpFile.deleteExisting() diff --git a/data/build.gradle.kts b/data/build.gradle.kts index b69d062f..bfb62094 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,3 +1,5 @@ +import com.google.devtools.ksp.gradle.KspAATask + plugins { id(libs.plugins.kotlin.multiplatform.get().pluginId) id(libs.plugins.kotlin.serialization.get().pluginId) @@ -141,3 +143,7 @@ apollo { } } } + +tasks.withType { + mustRunAfter("generateServiceApolloSources") +} diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index 2819fc79..0d607c77 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -1,6 +1,7 @@ import Config.migrationCode import Config.serverCode import Config.tachideskVersion +import com.google.devtools.ksp.gradle.KspAATask import org.gradle.jvm.tasks.Jar import org.jetbrains.compose.compose import org.jetbrains.compose.desktop.application.dsl.TargetFormat @@ -231,3 +232,11 @@ buildConfig { buildConfigField("String", "TACHIDESK_SP_VERSION", tachideskVersion.wrap()) buildConfigField("int", "SERVER_CODE", serverCode.toString()) } + +tasks.withType { + mustRunAfter( + "generateBuildConfig", + "generateResourceAccessorsForMain", + "generateComposeResClass" + ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ad6d9a1..f21197c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,37 +1,37 @@ [versions] # Kotlin -kotlin = "2.0.20" +kotlin = "2.2.20" coroutines = "1.9.0" # Serialization -json = "1.7.3" +json = "1.9.0" # Compose -composeGradle = "1.6.11" +composeGradle = "1.9.0" # Compose Libraries parcelize = "0.9.0" -voyager = "1.0.0" +voyager = "1.1.0-beta03" accompanist = "0.30.1" googleAccompanist = "0.30.1" imageloader = "1.8.1" materialDialogs = "0.9.5" # Android -androidGradle = "8.6.1" +androidGradle = "8.12.0" core = "1.13.1" appCompat = "1.7.0" activityCompose = "1.9.2" work = "2.9.1" # Android Lifecycle -lifecycle = "2.8.6" +lifecycle = "2.9.4" # Swing darklaf = "3.0.2" # Ksp -ksp = "2.0.20-1.0.25" +ksp = "2.2.20-2.0.3" # Dependency Injection kotlinInject = "0.7.2" @@ -54,7 +54,7 @@ appDirs = "1.2.0" multiplatformSettings = "1.2.0" # Utility -desugarJdkLibs = "2.1.2" +desugarJdkLibs = "2.1.5" aboutLibraries = "11.2.3" dateTime = "0.6.1" immutableCollections = "0.3.8" @@ -62,17 +62,17 @@ korge = "5.4.0" gradleDownloadTask = "5.6.0" # Localization -moko = "0.24.3" +moko = "0.25.1" # BuildConfigs -buildconfig = "5.5.0" -buildkonfig = "0.15.2" +buildconfig = "5.6.8" +buildkonfig = "0.17.1" # Linter kotlinter = "4.4.1" # Version updates -versions = "0.51.0" +versions = "0.53.0" # Optimizer proguard = "7.5.0" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 3889 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 delta 3990 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 wpAeHLsTu { + mustRunAfter( + "generateResourceAccessorsForDesktopMain", + "generateResourceAccessorsForJvmMain", + "generateResourceAccessorsForCommonMain", + "generateComposeResClass" + ) +} diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/navigation/Toolbar.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/navigation/Toolbar.kt index 4ce014d7..f1a21f09 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/navigation/Toolbar.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/navigation/Toolbar.kt @@ -47,7 +47,7 @@ import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.Sort import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Search -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -398,7 +398,7 @@ fun TextActionIcon( enabled = enabled, role = Role.Button, interactionSource = interactionSource, - indication = rememberRipple(bounded = false, radius = 32.dp), + indication = ripple(bounded = false, radius = 32.dp), ) .size(56.dp), verticalArrangement = Arrangement.SpaceAround, diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/components/DownloadsScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/components/DownloadsScreenContent.kt index 7c72645f..789efc31 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/components/DownloadsScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/downloads/components/DownloadsScreenContent.kt @@ -116,7 +116,7 @@ fun DownloadsScreenContent( ) { items(downloadQueue, key = { "${it.mangaId}-${it.chapterIndex}" }) { DownloadsItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), item = it, onClickCover = { onMangaClick(it.mangaId) }, onClickCancel = stopDownload, diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/extensions/components/ExtensionsScreenContent.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/extensions/components/ExtensionsScreenContent.kt index 3b04c16e..14d68d75 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/extensions/components/ExtensionsScreenContent.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/extensions/components/ExtensionsScreenContent.kt @@ -145,13 +145,13 @@ fun ExtensionsScreenContent( is ExtensionUI.Header -> Text( it.header, style = MaterialTheme.typography.h6, - modifier = Modifier.animateItemPlacement() + modifier = Modifier.animateItem() .padding(16.dp, 16.dp, 16.dp, 4.dp), ) is ExtensionUI.ExtensionItem -> Column { ExtensionItem( - Modifier.animateItemPlacement(), + Modifier.animateItem(), it, onInstallClicked = installExtension, onUpdateClicked = updateExtension, diff --git a/ui-core/src/androidMain/kotlin/ca/gosyer/jui/uicore/components/AndroidBottomActionMenu.kt b/ui-core/src/androidMain/kotlin/ca/gosyer/jui/uicore/components/AndroidBottomActionMenu.kt index f72bbcfa..bdf0bba8 100644 --- a/ui-core/src/androidMain/kotlin/ca/gosyer/jui/uicore/components/AndroidBottomActionMenu.kt +++ b/ui-core/src/androidMain/kotlin/ca/gosyer/jui/uicore/components/AndroidBottomActionMenu.kt @@ -8,7 +8,7 @@ package ca.gosyer.jui.uicore.components import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material.ripple import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed @@ -20,7 +20,7 @@ actual fun Modifier.buttonModifier( composed { combinedClickable( interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false), + indication = ripple(bounded = false), onLongClick = onHintClick, onClick = onClick, ) diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/DropdownIconButton.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/DropdownIconButton.kt index f33c68e8..03963a5a 100644 --- a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/DropdownIconButton.kt +++ b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/DropdownIconButton.kt @@ -13,7 +13,7 @@ import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.size import androidx.compose.material.DropdownMenu -import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -46,7 +46,7 @@ fun DropdownIconButton( .clickable( remember { MutableInteractionSource() }, role = Role.Button, - indication = rememberRipple(bounded = false, radius = 24.dp), + indication = ripple(bounded = false, radius = 24.dp), ) { showMenu = true } diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/pager/Pager.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/pager/Pager.kt index 0a3f17e2..ab77fc49 100644 --- a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/pager/Pager.kt +++ b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/pager/Pager.kt @@ -6,12 +6,10 @@ package ca.gosyer.jui.uicore.pager -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.FlingBehavior import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider -import androidx.compose.foundation.gestures.snapping.SnapPositionInLayout -import androidx.compose.foundation.gestures.snapping.SnapPositionInLayout.Companion.CenterToCenter +import androidx.compose.foundation.gestures.snapping.SnapPosition import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -26,9 +24,11 @@ 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.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable @@ -36,10 +36,14 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastMaxBy import kotlinx.coroutines.flow.distinctUntilChanged import kotlin.math.abs +import kotlin.math.absoluteValue import kotlin.math.sign @Composable @@ -234,103 +238,150 @@ class PagerState( // https://android.googlesource.com/platform/frameworks/support/+/refs/changes/78/2160778/35/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/snapping/LazyListSnapLayoutInfoProvider.kt private fun lazyListSnapLayoutInfoProvider( lazyListState: LazyListState, - positionInLayout: SnapPositionInLayout = CenterToCenter, -) = object : SnapLayoutInfoProvider { - private val layoutInfo: LazyListLayoutInfo - get() = lazyListState.layoutInfo + snapPosition: SnapPosition = SnapPosition.Center, + density: State, +): SnapLayoutInfoProvider = + object : SnapLayoutInfoProvider { - // Single page snapping is the default - override fun calculateApproachOffset(initialVelocity: Float): Float = 0f + private val layoutInfo: LazyListLayoutInfo + get() = lazyListState.layoutInfo - override fun calculateSnappingOffset(currentVelocity: Float): Float { - var lowerBoundOffset = Float.NEGATIVE_INFINITY - var upperBoundOffset = Float.POSITIVE_INFINITY - - layoutInfo.visibleItemsInfo.fastForEach { item -> - val offset = - calculateDistanceToDesiredSnapPosition( - mainAxisViewPortSize = layoutInfo.singleAxisViewportSize, - beforeContentPadding = layoutInfo.beforeContentPadding, - afterContentPadding = layoutInfo.afterContentPadding, - itemSize = item.size, - itemOffset = item.offset, - itemIndex = item.index, - snapPositionInLayout = positionInLayout, - ) - - // Find item that is closest to the center - if (offset <= 0 && offset > lowerBoundOffset) { - lowerBoundOffset = offset + private val averageItemSize: Int + get() { + val layoutInfo = layoutInfo + return if (layoutInfo.visibleItemsInfo.isEmpty()) { + 0 + } else { + val numberOfItems = layoutInfo.visibleItemsInfo.size + layoutInfo.visibleItemsInfo.sumOf { it.size } / numberOfItems + } } - // Find item that is closest to center, but after it - if (offset >= 0 && offset < upperBoundOffset) { - upperBoundOffset = offset - } + override fun calculateApproachOffset(velocity: Float, decayOffset: Float): Float { + return (decayOffset.absoluteValue - averageItemSize).coerceAtLeast(0.0f) * + decayOffset.sign } - return calculateFinalOffset( - currentVelocity, - lowerBoundOffset, - upperBoundOffset, - ) + override fun calculateSnapOffset(velocity: Float): Float { + var lowerBoundOffset = Float.NEGATIVE_INFINITY + var upperBoundOffset = Float.POSITIVE_INFINITY + + layoutInfo.visibleItemsInfo.fastForEach { item -> + val offset = + calculateDistanceToDesiredSnapPosition( + mainAxisViewPortSize = layoutInfo.singleAxisViewportSize, + beforeContentPadding = layoutInfo.beforeContentPadding, + afterContentPadding = layoutInfo.afterContentPadding, + itemSize = item.size, + itemOffset = item.offset, + itemIndex = item.index, + snapPosition = snapPosition, + itemCount = layoutInfo.totalItemsCount, + ) + + // Find item that is closest to the center + if (offset <= 0 && offset > lowerBoundOffset) { + lowerBoundOffset = offset + } + + // Find item that is closest to center, but after it + if (offset >= 0 && offset < upperBoundOffset) { + upperBoundOffset = offset + } + } + + return calculateFinalOffset( + with(density.value) { calculateFinalSnappingItem(velocity) }, + lowerBoundOffset, + upperBoundOffset, + ) + } } - @OptIn(ExperimentalFoundationApi::class) - private fun calculateDistanceToDesiredSnapPosition( - mainAxisViewPortSize: Int, - beforeContentPadding: Int, - afterContentPadding: Int, - itemSize: Int, - itemOffset: Int, - itemIndex: Int, - snapPositionInLayout: SnapPositionInLayout, - ): Float { - val containerSize = mainAxisViewPortSize - beforeContentPadding - afterContentPadding +@Composable +private fun rememberLazyListSnapFlingBehavior(lazyListState: LazyListState): FlingBehavior { + // return rememberSnapFlingBehavior(lazyListState) + val density = rememberUpdatedState(LocalDensity.current) + val snappingLayout = remember(lazyListState) { lazyListSnapLayoutInfoProvider(lazyListState, density = density) } + return rememberSnapFlingBehavior(snappingLayout) +} - val desiredDistance = with(snapPositionInLayout) { - position(containerSize, itemSize, beforeContentPadding, afterContentPadding, itemIndex) - }.toFloat() +internal val LazyListLayoutInfo.singleAxisViewportSize: Int + get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width - return itemOffset - desiredDistance +@kotlin.jvm.JvmInline +internal value class FinalSnappingItem +internal constructor(@Suppress("unused") private val value: Int) { + companion object { + + val ClosestItem: FinalSnappingItem = FinalSnappingItem(0) + + val NextItem: FinalSnappingItem = FinalSnappingItem(1) + + val PreviousItem: FinalSnappingItem = FinalSnappingItem(2) + } +} + +internal fun Density.calculateFinalSnappingItem(velocity: Float): FinalSnappingItem { + return if (velocity.absoluteValue < 400.dp.toPx()) { + FinalSnappingItem.ClosestItem + } else { + if (velocity > 0) FinalSnappingItem.NextItem else FinalSnappingItem.PreviousItem + } +} + +internal fun calculateFinalOffset( + snappingOffset: FinalSnappingItem, + lowerBound: Float, + upperBound: Float, +): Float { + fun Float.isValidDistance(): Boolean { + return this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY } - private fun calculateFinalOffset( - velocity: Float, - lowerBound: Float, - upperBound: Float, - ): Float { - fun Float.isValidDistance(): Boolean = this != Float.POSITIVE_INFINITY && this != Float.NEGATIVE_INFINITY - - val finalDistance = when (sign(velocity)) { - 0f -> { + val finalDistance = + when (snappingOffset) { + FinalSnappingItem.ClosestItem -> { if (abs(upperBound) <= abs(lowerBound)) { upperBound } else { lowerBound } } - - 1f -> upperBound - - -1f -> lowerBound - + FinalSnappingItem.NextItem -> upperBound + FinalSnappingItem.PreviousItem -> lowerBound else -> 0f } - return if (finalDistance.isValidDistance()) { - finalDistance - } else { - 0f - } + return if (finalDistance.isValidDistance()) { + finalDistance + } else { + 0f } } -@Composable -private fun rememberLazyListSnapFlingBehavior(lazyListState: LazyListState): FlingBehavior { - val snappingLayout = remember(lazyListState) { lazyListSnapLayoutInfoProvider(lazyListState) } - return rememberSnapFlingBehavior(snappingLayout) -} +internal fun calculateDistanceToDesiredSnapPosition( + mainAxisViewPortSize: Int, + beforeContentPadding: Int, + afterContentPadding: Int, + itemSize: Int, + itemOffset: Int, + itemIndex: Int, + snapPosition: SnapPosition, + itemCount: Int, +): Float { + val desiredDistance = + with(snapPosition) { + position( + mainAxisViewPortSize, + itemSize, + beforeContentPadding, + afterContentPadding, + itemIndex, + itemCount, + ) + } + .toFloat() -private val LazyListLayoutInfo.singleAxisViewportSize: Int - get() = if (orientation == Orientation.Vertical) viewportSize.height else viewportSize.width + return itemOffset - desiredDistance +}