diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index f7ebda3b7..5e3d33135 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app" // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 66 -const val STRAW_VERSION_NAME = "0.1.0-BZ" +const val STRAW_VERSION_CODE = 67 +const val STRAW_VERSION_NAME = "0.1.0-CA" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt b/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt index 06acdf2cb..94ac9fe69 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt @@ -323,7 +323,11 @@ private fun SubsPane( visibleCount = PAGE_SIZE } } - val displayed = filteredItems.take(visibleCount) + // remember the page-slice so we don't allocate a new ArrayList on + // every recomposition (scroll hitch vc=67). + val displayed = remember(filteredItems, visibleCount) { + filteredItems.take(visibleCount) + } val hasMore = filteredItems.size > visibleCount Column { @@ -442,7 +446,10 @@ private fun SubsPane( state = listState, contentPadding = rememberBottomContentPadding(), ) { - items(displayed) { item -> + items( + items = displayed, + key = { it.url }, + ) { item -> FeedRow( item = item, onClick = { onOpenVideo(item.url, item.title) }, diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ThumbnailProgress.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ThumbnailProgress.kt index bc4bd4e4f..26daa53f0 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ThumbnailProgress.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/ThumbnailProgress.kt @@ -25,13 +25,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.sulkta.straw.OverlayDimColor import com.sulkta.straw.ProgressBarFillColor @@ -51,7 +51,13 @@ import com.sulkta.straw.util.formatDuration @Composable fun BoxScope.ThumbnailProgressOverlay(videoId: String?) { if (videoId.isNullOrBlank()) return - val positions by Resume.get().positions.collectAsStateWithLifecycle() + // Plain collectAsState — collectAsStateWithLifecycle adds a + // DisposableEffect for lifecycle observation per call site, which + // adds up across 30 visible LazyColumn rows and contributes to + // scroll jank (vc=67). The Lifecycle pause optimization doesn't + // matter for a foreground feed that's only collected while the + // composable is on screen anyway. + val positions by Resume.get().positions.collectAsState() val entry = positions[videoId] ?: return if (entry.durationMs <= 0L) return val fraction = (entry.positionMs.toFloat() / entry.durationMs.toFloat())