diff --git a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png index 25283c111..523b03ec5 100644 Binary files a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png differ diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/LazyScrollbarUtilities.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/LazyScrollbarUtilities.kt index 8c4063b15..57e567b5d 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/LazyScrollbarUtilities.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/LazyScrollbarUtilities.kt @@ -17,79 +17,7 @@ package com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar import androidx.compose.foundation.gestures.ScrollableState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull import kotlin.math.abs -import kotlin.math.min - -/** - * Calculates the [ScrollbarState] for lazy layouts. - * @param itemsAvailable the total amount of items available to scroll in the layout. - * @param visibleItems a list of items currently visible in the layout. - * @param firstVisibleItemIndex a function for interpolating the first visible index in the lazy layout - * as scrolling progresses for smooth and linear scrollbar thumb progression. - * [itemsAvailable]. - * @param reverseLayout if the items in the backing lazy layout are laid out in reverse order. - * */ -@Composable -internal inline fun LazyState.scrollbarState( - itemsAvailable: Int, - crossinline visibleItems: LazyState.() -> List, - crossinline firstVisibleItemIndex: LazyState.(List) -> Float, - crossinline itemPercentVisible: LazyState.(LazyStateItem) -> Float, - crossinline reverseLayout: LazyState.() -> Boolean, -): ScrollbarState { - var state by remember { mutableStateOf(ScrollbarState.FULL) } - - LaunchedEffect( - key1 = this, - key2 = itemsAvailable, - ) { - snapshotFlow { - if (itemsAvailable == 0) return@snapshotFlow null - - val visibleItemsInfo = visibleItems(this@scrollbarState) - if (visibleItemsInfo.isEmpty()) return@snapshotFlow null - - val firstIndex = min( - a = firstVisibleItemIndex(visibleItemsInfo), - b = itemsAvailable.toFloat(), - ) - if (firstIndex.isNaN()) return@snapshotFlow null - - val itemsVisible = visibleItemsInfo.sumOf { - itemPercentVisible(it).toDouble() - }.toFloat() - - val thumbTravelPercent = min( - a = firstIndex / itemsAvailable, - b = 1f, - ) - val thumbSizePercent = min( - a = itemsVisible / itemsAvailable, - b = 1f, - ) - ScrollbarState( - thumbSizePercent = thumbSizePercent, - thumbMovedPercent = when { - reverseLayout() -> 1f - thumbTravelPercent - else -> thumbTravelPercent - }, - ) - } - .filterNotNull() - .distinctUntilChanged() - .collect { state = it } - } - return state -} /** * Linearly interpolates the index for the first item in [visibleItems] for smooth scrollbar diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt index 26f0bb2ae..7a0282bf7 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,14 @@ import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridItemInfo import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemInfo +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.runtime.Composable +import androidx.compose.runtime.produceState +import androidx.compose.runtime.snapshotFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlin.math.min /** * Calculates a [ScrollbarState] driven by the changes in a [LazyListState]. @@ -33,29 +40,58 @@ import androidx.compose.runtime.Composable fun LazyListState.scrollbarState( itemsAvailable: Int, itemIndex: (LazyListItemInfo) -> Int = LazyListItemInfo::index, -): ScrollbarState = - scrollbarState( - itemsAvailable = itemsAvailable, - visibleItems = { layoutInfo.visibleItemsInfo }, - firstVisibleItemIndex = { visibleItems -> - interpolateFirstItemIndex( - visibleItems = visibleItems, +): ScrollbarState = produceState( + initialValue = ScrollbarState.FULL, + key1 = this, + key2 = itemsAvailable, +) { + snapshotFlow { + if (itemsAvailable == 0) return@snapshotFlow null + + val visibleItemsInfo = layoutInfo.visibleItemsInfo + if (visibleItemsInfo.isEmpty()) return@snapshotFlow null + + val firstIndex = min( + a = interpolateFirstItemIndex( + visibleItems = visibleItemsInfo, itemSize = { it.size }, offset = { it.offset }, - nextItemOnMainAxis = { first -> visibleItems.find { it != first } }, + nextItemOnMainAxis = { first -> visibleItemsInfo.find { it != first } }, itemIndex = itemIndex, - ) - }, - itemPercentVisible = itemPercentVisible@{ itemInfo -> + ), + b = itemsAvailable.toFloat(), + ) + if (firstIndex.isNaN()) return@snapshotFlow null + + val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo -> itemVisibilityPercentage( itemSize = itemInfo.size, itemStartOffset = itemInfo.offset, viewportStartOffset = layoutInfo.viewportStartOffset, viewportEndOffset = layoutInfo.viewportEndOffset, ) - }, - reverseLayout = { layoutInfo.reverseLayout }, - ) + } + + val thumbTravelPercent = min( + a = firstIndex / itemsAvailable, + b = 1f, + ) + val thumbSizePercent = min( + a = itemsVisible / itemsAvailable, + b = 1f, + ) + ScrollbarState( + thumbSizePercent = thumbSizePercent, + thumbMovedPercent = when { + layoutInfo.reverseLayout -> 1f - thumbTravelPercent + else -> thumbTravelPercent + }, + ) + } + .filterNotNull() + .distinctUntilChanged() + .collect { value = it } +}.value /** * Calculates a [ScrollbarState] driven by the changes in a [LazyGridState] @@ -67,38 +103,136 @@ fun LazyListState.scrollbarState( fun LazyGridState.scrollbarState( itemsAvailable: Int, itemIndex: (LazyGridItemInfo) -> Int = LazyGridItemInfo::index, -): ScrollbarState = - scrollbarState( - itemsAvailable = itemsAvailable, - visibleItems = { layoutInfo.visibleItemsInfo }, - firstVisibleItemIndex = { visibleItems -> - interpolateFirstItemIndex( - visibleItems = visibleItems, - itemSize = { - layoutInfo.orientation.valueOf(it.size) - }, +): ScrollbarState = produceState( + initialValue = ScrollbarState.FULL, + key1 = this, + key2 = itemsAvailable, +) { + snapshotFlow { + if (itemsAvailable == 0) return@snapshotFlow null + + val visibleItemsInfo = layoutInfo.visibleItemsInfo + if (visibleItemsInfo.isEmpty()) return@snapshotFlow null + + val firstIndex = min( + a = interpolateFirstItemIndex( + visibleItems = visibleItemsInfo, + itemSize = { layoutInfo.orientation.valueOf(it.size) }, offset = { layoutInfo.orientation.valueOf(it.offset) }, nextItemOnMainAxis = { first -> when (layoutInfo.orientation) { - Orientation.Vertical -> visibleItems.find { + Orientation.Vertical -> visibleItemsInfo.find { it != first && it.row != first.row } - Orientation.Horizontal -> visibleItems.find { + Orientation.Horizontal -> visibleItemsInfo.find { it != first && it.column != first.column } } }, itemIndex = itemIndex, + ), + b = itemsAvailable.toFloat(), + ) + if (firstIndex.isNaN()) return@snapshotFlow null + + val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo -> + itemVisibilityPercentage( + itemSize = layoutInfo.orientation.valueOf(itemInfo.size), + itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset), + viewportStartOffset = layoutInfo.viewportStartOffset, + viewportEndOffset = layoutInfo.viewportEndOffset, ) - }, - itemPercentVisible = itemPercentVisible@{ itemInfo -> + } + + val thumbTravelPercent = min( + a = firstIndex / itemsAvailable, + b = 1f, + ) + val thumbSizePercent = min( + a = itemsVisible / itemsAvailable, + b = 1f, + ) + ScrollbarState( + thumbSizePercent = thumbSizePercent, + thumbMovedPercent = when { + layoutInfo.reverseLayout -> 1f - thumbTravelPercent + else -> thumbTravelPercent + }, + ) + } + .filterNotNull() + .distinctUntilChanged() + .collect { value = it } +}.value + +/** + * Remembers a [ScrollbarState] driven by the changes in a [LazyStaggeredGridState] + * + * @param itemsAvailable the total amount of items available to scroll in the staggered grid. + * @param itemIndex a lookup function for index of an item in the staggered grid relative + * to [itemsAvailable]. + */ +@Composable +fun LazyStaggeredGridState.scrollbarState( + itemsAvailable: Int, + itemIndex: (LazyStaggeredGridItemInfo) -> Int = LazyStaggeredGridItemInfo::index, +): ScrollbarState = produceState( + initialValue = ScrollbarState.FULL, + key1 = this, + key2 = itemsAvailable, +) { + snapshotFlow { + if (itemsAvailable == 0) return@snapshotFlow null + + val visibleItemsInfo = layoutInfo.visibleItemsInfo + if (visibleItemsInfo.isEmpty()) return@snapshotFlow null + + val firstIndex = min( + a = interpolateFirstItemIndex( + visibleItems = visibleItemsInfo, + itemSize = { layoutInfo.orientation.valueOf(it.size) }, + offset = { layoutInfo.orientation.valueOf(it.offset) }, + nextItemOnMainAxis = { first -> + visibleItemsInfo.find { it != first && it.lane == first.lane } + }, + itemIndex = itemIndex, + ), + b = itemsAvailable.toFloat(), + ) + if (firstIndex.isNaN()) return@snapshotFlow null + + val itemsVisible = visibleItemsInfo.floatSumOf { itemInfo -> itemVisibilityPercentage( itemSize = layoutInfo.orientation.valueOf(itemInfo.size), itemStartOffset = layoutInfo.orientation.valueOf(itemInfo.offset), viewportStartOffset = layoutInfo.viewportStartOffset, viewportEndOffset = layoutInfo.viewportEndOffset, ) - }, - reverseLayout = { layoutInfo.reverseLayout }, - ) + } + + val thumbTravelPercent = min( + a = firstIndex / itemsAvailable, + b = 1f, + ) + val thumbSizePercent = min( + a = itemsVisible / itemsAvailable, + b = 1f, + ) + ScrollbarState( + thumbSizePercent = thumbSizePercent, + thumbMovedPercent = thumbTravelPercent, + ) + } + .filterNotNull() + .distinctUntilChanged() + .collect { value = it } +}.value + +private inline fun List.floatSumOf(selector: (T) -> Float): Float { + var sum = 0f + for (element in this) { + sum += selector(element) + } + return sum +} diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt index 055779800..847580361 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt @@ -18,6 +18,7 @@ package com.google.samples.apps.nowinandroid.core.designsystem.component.scrollb import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -50,6 +51,19 @@ fun LazyGridState.rememberDraggableScroller( scroll = ::scrollToItem, ) +/** + * Remembers a function to react to [Scrollbar] thumb position displacements for a + * [LazyStaggeredGridState] + * @param itemsAvailable the amount of items in the staggered grid. + */ +@Composable +fun LazyStaggeredGridState.rememberDraggableScroller( + itemsAvailable: Int, +): (Float) -> Unit = rememberDraggableScroller( + itemsAvailable = itemsAvailable, + scroll = ::scrollToItem, +) + /** * Generic function to react to [Scrollbar] thumb displacements in a lazy layout. * @param itemsAvailable the total amount of items available to scroll in the layout. diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index 4a9f5d7c9..e2904afc3 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -21,12 +21,13 @@ import android.net.Uri import androidx.annotation.ColorInt import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -47,7 +48,8 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource * An extension on [LazyListScope] defining a feed with news resources. * Depending on the [feedState], this might emit no items. */ -fun LazyGridScope.newsFeed( +@OptIn(ExperimentalFoundationApi::class) +fun LazyStaggeredGridScope.newsFeed( feedState: NewsFeedUiState, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, onNewsResourceViewed: (String) -> Unit, @@ -88,7 +90,9 @@ fun LazyGridScope.newsFeed( ) }, onTopicClick = onTopicClick, - modifier = Modifier.padding(horizontal = 8.dp), + modifier = Modifier + .padding(horizontal = 8.dp) + .animateItemPlacement(), ) } } @@ -129,7 +133,7 @@ sealed interface NewsFeedUiState { @Composable private fun NewsFeedLoadingPreview() { NiaTheme { - LazyVerticalGrid(columns = GridCells.Adaptive(300.dp)) { + LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Adaptive(300.dp)) { newsFeed( feedState = NewsFeedUiState.Loading, onNewsResourcesCheckedChanged = { _, _ -> }, @@ -148,7 +152,7 @@ private fun NewsFeedContentPreview( userNewsResources: List, ) { NiaTheme { - LazyVerticalGrid(columns = GridCells.Adaptive(300.dp)) { + LazyVerticalStaggeredGrid(columns = StaggeredGridCells.Adaptive(300.dp)) { newsFeed( feedState = NewsFeedUiState.Success(userNewsResources), onNewsResourcesCheckedChanged = { _, _ -> }, diff --git a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt index e46ada015..7d51c6e84 100644 --- a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt +++ b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt @@ -35,10 +35,10 @@ import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.grid.GridCells.Adaptive -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -175,17 +175,17 @@ private fun BookmarksGrid( onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, ) { - val scrollableState = rememberLazyGridState() + val scrollableState = rememberLazyStaggeredGridState() TrackScrollJank(scrollableState = scrollableState, stateName = "bookmarks:grid") Box( modifier = modifier .fillMaxSize(), ) { - LazyVerticalGrid( - columns = Adaptive(300.dp), + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(300.dp), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(24.dp), + verticalItemSpacing = 24.dp, state = scrollableState, modifier = Modifier .fillMaxSize() @@ -197,7 +197,7 @@ private fun BookmarksGrid( onNewsResourceViewed = onNewsResourceViewed, onTopicClick = onTopicClick, ) - item(span = { GridItemSpan(maxLineSpan) }) { + item(span = StaggeredGridItemSpan.FullLine) { Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) } } diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index a24a91f1a..65b5ecbc4 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -49,13 +49,14 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridCells.Adaptive -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon @@ -153,7 +154,7 @@ internal fun ForYouScreen( val itemsAvailable = feedItemsSize(feedState, onboardingUiState) - val state = rememberLazyGridState() + val state = rememberLazyStaggeredGridState() val scrollbarState = state.scrollbarState( itemsAvailable = itemsAvailable, ) @@ -163,11 +164,11 @@ internal fun ForYouScreen( modifier = modifier .fillMaxSize(), ) { - LazyVerticalGrid( - columns = Adaptive(300.dp), + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(300.dp), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(24.dp), + verticalItemSpacing = 24.dp, modifier = Modifier .testTag("forYou:feed"), state = state, @@ -197,7 +198,7 @@ internal fun ForYouScreen( onTopicClick = onTopicClick, ) - item(span = { GridItemSpan(maxLineSpan) }, contentType = "bottomSpacing") { + item(span = StaggeredGridItemSpan.FullLine, contentType = "bottomSpacing") { Column { Spacer(modifier = Modifier.height(8.dp)) // Add space for the content to clear the "offline" snackbar. @@ -255,7 +256,7 @@ internal fun ForYouScreen( * Depending on the [onboardingUiState], this might emit no items. * */ -private fun LazyGridScope.onboarding( +private fun LazyStaggeredGridScope.onboarding( onboardingUiState: OnboardingUiState, onTopicCheckedChanged: (String, Boolean) -> Unit, saveFollowedTopics: () -> Unit, @@ -268,7 +269,7 @@ private fun LazyGridScope.onboarding( -> Unit is OnboardingUiState.Shown -> { - item(span = { GridItemSpan(maxLineSpan) }, contentType = "onboarding") { + item(span = StaggeredGridItemSpan.FullLine, contentType = "onboarding") { Column(modifier = interestsItemModifier) { Text( text = stringResource(R.string.onboarding_guidance_title), diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png index a66604bc6..deb0cd855 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png index 54ae3be02..a12c429d9 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png differ diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt index 944d17630..65b65f61d 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt @@ -35,11 +35,11 @@ import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.grid.GridCells.Adaptive -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan +import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.KeyboardActions @@ -297,16 +297,16 @@ private fun SearchResultBody( onTopicClick: (String) -> Unit, searchQuery: String = "", ) { - val state = rememberLazyGridState() + val state = rememberLazyStaggeredGridState() Box( modifier = Modifier .fillMaxSize(), ) { - LazyVerticalGrid( - columns = Adaptive(300.dp), + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(300.dp), contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalArrangement = Arrangement.spacedBy(24.dp), + verticalItemSpacing = 24.dp, modifier = Modifier .fillMaxSize() .testTag("search:newsResources"), @@ -314,9 +314,7 @@ private fun SearchResultBody( ) { if (topics.isNotEmpty()) { item( - span = { - GridItemSpan(maxLineSpan) - }, + span = StaggeredGridItemSpan.FullLine, ) { Text( text = buildAnnotatedString { @@ -331,9 +329,7 @@ private fun SearchResultBody( val topicId = followableTopic.topic.id item( key = "topic-$topicId", // Append a prefix to distinguish a key for news resources - span = { - GridItemSpan(maxLineSpan) - }, + span = StaggeredGridItemSpan.FullLine, ) { InterestsItem( name = followableTopic.topic.name, @@ -353,9 +349,7 @@ private fun SearchResultBody( if (newsResources.isNotEmpty()) { item( - span = { - GridItemSpan(maxLineSpan) - }, + span = StaggeredGridItemSpan.FullLine, ) { Text( text = buildAnnotatedString { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39de85dc2..5bff27d34 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,8 +5,8 @@ androidGradlePlugin = "8.1.1" androidxActivity = "1.8.0-alpha06" androidxAppCompat = "1.5.1" androidxBrowser = "1.4.0" -androidxComposeBom = "2023.06.01" -androidxComposeCompiler = "1.5.0" +androidxComposeBom = "2023.09.00" +androidxComposeCompiler = "1.5.3" androidxComposeRuntimeTracing = "1.0.0-alpha03" androidxCore = "1.9.0" androidxCoreSplashscreen = "1.0.0" @@ -38,11 +38,11 @@ hilt = "2.47" hiltExt = "1.0.0" jacoco = "0.8.7" junit4 = "4.13.2" -kotlin = "1.9.0" +kotlin = "1.9.10" kotlinxCoroutines = "1.6.4" kotlinxDatetime = "0.4.0" kotlinxSerializationJson = "1.5.1" -ksp = "1.9.0-1.0.13" +ksp = "1.9.10-1.0.13" lint = "31.0.2" okhttp = "4.10.0" protobuf = "3.24.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34e8..3fa8f862f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a5..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \