Skip to content

[Jetcaster] Improve scroll performance of podcast pager #1106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Crane/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 2 additions & 0 deletions JetLagged/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 2 additions & 0 deletions JetNews/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 1 addition & 1 deletion Jetcaster/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ dependencies {

implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.collections.immutable)

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.palette)
Expand All @@ -112,7 +113,6 @@ dependencies {
implementation(libs.androidx.window)

implementation(libs.accompanist.adaptive)
implementation(libs.accompanist.pager)

implementation(libs.coil.kt.compose)

Expand Down
30 changes: 15 additions & 15 deletions Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.example.jetcaster.ui.home

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
Expand All @@ -37,6 +38,9 @@ import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
Expand Down Expand Up @@ -82,13 +86,10 @@ import com.example.jetcaster.util.contrastAgainst
import com.example.jetcaster.util.quantityStringResource
import com.example.jetcaster.util.rememberDominantColorState
import com.example.jetcaster.util.verticalGradientScrim
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState
import java.time.Duration
import java.time.LocalDateTime
import java.time.OffsetDateTime
import kotlinx.collections.immutable.PersistentList

@Composable
fun Home(
Expand Down Expand Up @@ -156,10 +157,10 @@ fun HomeAppBar(
)
}

@OptIn(ExperimentalPagerApi::class) // HorizontalPager is experimental
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HomeContent(
featuredPodcasts: List<PodcastWithExtraInfo>,
featuredPodcasts: PersistentList<PodcastWithExtraInfo>,
isRefreshing: Boolean,
selectedHomeCategory: HomeCategory,
homeCategories: List<HomeCategory>,
Expand All @@ -177,6 +178,7 @@ fun HomeContent(
// 'top podcast'

val surfaceColor = MaterialTheme.colors.surface
val appBarColor = surfaceColor.copy(alpha = 0.87f)
val dominantColorState = rememberDominantColorState { color ->
// We want a color which has sufficient contrast against the surface color
color.contrastAgainst(surfaceColor) >= MinContrastOfPrimaryVsSurface
Expand Down Expand Up @@ -206,8 +208,6 @@ fun HomeContent(
endYPercentage = 0f
)
) {
val appBarColor = MaterialTheme.colors.surface.copy(alpha = 0.87f)

// Draw a scrim over the status bar which matches the app bar
Spacer(
Modifier
Expand Down Expand Up @@ -317,25 +317,25 @@ fun HomeCategoryTabIndicator(
)
}

@ExperimentalPagerApi // HorizontalPager is experimental
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FollowedPodcasts(
items: List<PodcastWithExtraInfo>,
items: PersistentList<PodcastWithExtraInfo>,
pagerState: PagerState,
modifier: Modifier = Modifier,
onPodcastUnfollowed: (String) -> Unit,
) {
HorizontalPager(
count = items.size,
pageCount = items.size,
state = pagerState,
modifier = modifier
) { page ->
val (podcast, lastEpisodeDate) = items[page]
FollowedPodcastCarouselItem(
podcastImageUrl = podcast.imageUrl,
podcastTitle = podcast.title,
lastEpisodeDate = lastEpisodeDate,
onUnfollowedClick = { onPodcastUnfollowed(podcast.uri) },
lastEpisodeDateText = lastEpisodeDate?.let { lastUpdated(it) },
modifier = Modifier
.padding(4.dp)
.fillMaxHeight()
Expand All @@ -348,7 +348,7 @@ private fun FollowedPodcastCarouselItem(
modifier: Modifier = Modifier,
podcastImageUrl: String? = null,
podcastTitle: String? = null,
lastEpisodeDate: OffsetDateTime? = null,
lastEpisodeDateText: String? = null,
onUnfollowedClick: () -> Unit,
) {
Column(
Expand Down Expand Up @@ -378,10 +378,10 @@ private fun FollowedPodcastCarouselItem(
)
}

if (lastEpisodeDate != null) {
if (lastEpisodeDateText != null) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = lastUpdated(lastEpisodeDate),
text = lastEpisodeDateText,
style = MaterialTheme.typography.caption,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import com.example.jetcaster.Graph
import com.example.jetcaster.data.PodcastStore
import com.example.jetcaster.data.PodcastWithExtraInfo
import com.example.jetcaster.data.PodcastsRepository
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
Expand Down Expand Up @@ -59,7 +62,7 @@ class HomeViewModel(
HomeViewState(
homeCategories = categories,
selectedHomeCategory = selectedCategory,
featuredPodcasts = podcasts,
featuredPodcasts = podcasts.toPersistentList(),
refreshing = refreshing,
errorMessage = null /* TODO */
)
Expand Down Expand Up @@ -102,7 +105,7 @@ enum class HomeCategory {
}

data class HomeViewState(
val featuredPodcasts: List<PodcastWithExtraInfo> = emptyList(),
val featuredPodcasts: PersistentList<PodcastWithExtraInfo> = persistentListOf(),
val refreshing: Boolean = false,
val selectedHomeCategory: HomeCategory = HomeCategory.Discover,
val homeCategories: List<HomeCategory> = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ package com.example.jetcaster.util

import androidx.annotation.FloatRange
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
Expand All @@ -50,9 +54,18 @@ fun Modifier.verticalGradientScrim(
@FloatRange(from = 0.0, to = 1.0) endYPercentage: Float = 1f,
decay: Float = 1.0f,
numStops: Int = 16
): Modifier = composed {
val colors = remember(color, numStops) {
if (decay != 1f) {
) = this then VerticalGradientElement(color, startYPercentage, endYPercentage, decay, numStops)

@OptIn(ExperimentalComposeUiApi::class)
private data class VerticalGradientElement(
var color: Color,
var startYPercentage: Float = 0f,
var endYPercentage: Float = 1f,
var decay: Float = 1.0f,
var numStops: Int = 16
) : ModifierNodeElement<VerticalGradientModifier>() {
fun createOnDraw(): DrawScope.() -> Unit {
val colors = if (decay != 1f) {
// If we have a non-linear decay, we need to create the color gradient steps
// manually
val baseAlpha = color.alpha
Expand All @@ -65,25 +78,52 @@ fun Modifier.verticalGradientScrim(
// If we have a linear decay, we just create a simple list of start + end colors
listOf(color.copy(alpha = 0f), color)
}

val brush =
// Reverse the gradient if decaying downwards
Brush.verticalGradient(
colors = if (startYPercentage < endYPercentage) colors else colors.reversed(),
)

return {
val topLeft = Offset(0f, size.height * min(startYPercentage, endYPercentage))
val bottomRight =
Offset(size.width, size.height * max(startYPercentage, endYPercentage))

drawRect(
topLeft = topLeft,
size = Rect(topLeft, bottomRight).size,
brush = brush
)
}
}

val brush = remember(colors, startYPercentage, endYPercentage) {
// Reverse the gradient if decaying downwards
Brush.verticalGradient(
colors = if (startYPercentage < endYPercentage) colors else colors.reversed(),
)
override fun create() = VerticalGradientModifier(createOnDraw())

override fun update(node: VerticalGradientModifier) = node.apply {
node.onDraw = createOnDraw()
}

/**
* Allow this custom modifier to be inspected in the layout inspector
**/
override fun InspectorInfo.inspectableProperties() {
name = "verticalGradientScrim"
properties["color"] = color
properties["startYPercentage"] = startYPercentage
properties["endYPercentage"] = endYPercentage
properties["decay"] = decay
properties["numStops"] = numStops
}
}

drawBehind {
// Calculate the topLeft and bottomRight with the invariant that topLeft is actually above
// and left of bottomRight
val topLeft = Offset(0f, size.height * min(startYPercentage, endYPercentage))
val bottomRight = Offset(size.width, size.height * max(startYPercentage, endYPercentage))
@OptIn(ExperimentalComposeUiApi::class)
private class VerticalGradientModifier(
var onDraw: DrawScope.() -> Unit
) : Modifier.Node(), DrawModifierNode {

drawRect(
topLeft = topLeft,
size = Rect(topLeft, bottomRight).size,
brush = brush
)
override fun ContentDrawScope.draw() {
onDraw()
drawContent()
}
}
2 changes: 2 additions & 0 deletions Jetcaster/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 2 additions & 0 deletions Jetchat/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 2 additions & 0 deletions Jetsnack/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 2 additions & 0 deletions Jetsurvey/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
2 changes: 2 additions & 0 deletions Owl/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jdkDesugar = "1.2.2"
junit = "4.13.2"
# @pin Update in conjuction with Compose Compiler
kotlin = "1.8.0"
kotlinx_immutable = "0.3.5"
ksp = "1.8.0-1.0.9"
maps-compose = "2.5.3"
material = "1.9.0-beta01"
Expand Down Expand Up @@ -119,6 +120,7 @@ junit = { module = "junit:junit", version.ref = "junit" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx_immutable" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
Expand Down
Loading