Repository: android/compose-samples
File: Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt (Kotlin)

Code before:
private val FEATURED_PODCAST_IMAGE_HEIGHT_DP = 180.dp

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FollowedPodcasts(
    pagerState: PagerState,
    items: PersistentList<PodcastWithExtraInfo>,
    modifier: Modifier = Modifier,
    onPodcastUnfollowed: (String) -> Unit,
) {
    val coroutineScope = rememberCoroutineScope()

    var horizontalPadding by remember { mutableStateOf(0.dp) }
    val density = LocalDensity.current
    val screenWidth = LocalConfiguration.current.screenWidthDp.dp
    HorizontalPager(
        state = pagerState,
        modifier = modifier.onSizeChanged { size ->
            // TODO: this is not quite performant since it requires 2 passes to compute the content
            // padding. This should be revisited once a carousel component is available.
            // Alternatively, version 1.7.0-alpha05 of Compose Foundation supports `snapPosition`
            // which solves this problem and avoids this calculation altogether. Once 1.7.0 is
            // stable, this implementation can be updated.
            horizontalPadding = with(density) {
                (size.width.toDp() - FEATURED_PODCAST_IMAGE_WIDTH_DP) / 2
            }
        },
        contentPadding = PaddingValues(
            horizontal = horizontalPadding,
            vertical = 16.dp,
        ),
        pageSize = PageSize.Fixed(180.dp)
    ) { page ->
        val (podcast, lastEpisodeDate) = items[page]
        FollowedPodcastCarouselItem(
            podcastImageUrl = podcast.imageUrl,
            podcastTitle = podcast.title,
            onUnfollowedClick = { onPodcastUnfollowed(podcast.uri) },
            lastEpisodeDateText = lastEpisodeDate?.let { lastUpdated(it) },
            modifier = Modifier
                .fillMaxSize()
                .clickable {
                    coroutineScope.launch {
                        pagerState.animateScrollToPage(page)
                    }
                }
        )
    }
}

@Composable

Review comment (suggestion):
`BoxWithConstraints` might be more desirable here because this two frame padding calculation is visible to the user. It seems weird to me you have to do this in the first place though, I would have thought you could do some combination of pageSpacing, custom page size and horizontal padding to get it to centre

Code after:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FollowedPodcasts(
    pagerState: PagerState,
    items: PersistentList<PodcastWithExtraInfo>,
    modifier: Modifier = Modifier,
    onPodcastUnfollowed: (String) -> Unit,
) {
    val coroutineScope = rememberCoroutineScope()
    // TODO: Using BoxWithConstraints is not quite performant since it requires 2 passes to compute
    // the content padding. This should be revisited once a carousel component is available.
    // Alternatively, version 1.7.0-alpha05 of Compose Foundation supports `snapPosition`
    // which solves this problem and avoids this calculation altogether. Once 1.7.0 is
    // stable, this implementation can be updated.
    BoxWithConstraints(modifier) {
        val horizontalPadding = (this.maxWidth - FEATURED_PODCAST_IMAGE_WIDTH_DP) / 2
        HorizontalPager(
            state = pagerState,
            contentPadding = PaddingValues(
                horizontal = horizontalPadding,
                vertical = 16.dp,
            ),
            pageSpacing = 24.dp,
            pageSize = PageSize.Fixed(FEATURED_PODCAST_IMAGE_WIDTH_DP)
        ) { page ->
            val (podcast, lastEpisodeDate) = items[page]
            FollowedPodcastCarouselItem(
                podcastImageUrl = podcast.imageUrl,
                podcastTitle = podcast.title,
                onUnfollowedClick = { onPodcastUnfollowed(podcast.uri) },
                lastEpisodeDateText = lastEpisodeDate?.let { lastUpdated(it) },
                modifier = Modifier
                    .fillMaxSize()
                    .clickable {
                        coroutineScope.launch {
                            pagerState.animateScrollToPage(page)
                        }
                    }
            )
        }
    }
}

@Composable
private fun FollowedPodcastCarouselItem(
    modifier: Modifier = Modifier,
    podcastImageUrl: String? = null,
    podcastTitle: String? = null,
    lastEpisodeDateText: String? = null,
    onUnfollowedClick: () -> Unit,