Even better names

Change-Id: Ia699c3ce8fd1ce7a6e406b00c81dc196b487ed65
pull/722/head
TJ Dahunsi 2 years ago
parent ce6eaa9d64
commit cb1d50e65e

@ -23,6 +23,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.Orientation.Horizontal
import androidx.compose.foundation.gestures.Orientation.Vertical
import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsDraggedAsState
@ -56,18 +57,15 @@ private const val INACTIVE_TO_DORMANT_COOL_DOWN = 2_000L
* Its thumb disappears when the scrolling container is dormant.
* @param modifier a [Modifier] for the [Scrollbar]
* @param state the driving state for the [Scrollbar]
* @param scrollInProgress a flag indicating if the scrolling container for the scrollbar is
* currently scrolling
* @param orientation the orientation of the scrollbar
* @param onThumbMoved the fast scroll implementation
* @param onThumbDisplaced the fast scroll implementation
*/
@Composable
fun FastScrollbar(
fun ScrollableState.FastScrollbar(
modifier: Modifier = Modifier,
state: ScrollbarState,
scrollInProgress: Boolean,
orientation: Orientation,
onThumbMoved: (Float) -> Unit,
onThumbDisplaced: (Float) -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
Scrollbar(
@ -77,12 +75,11 @@ fun FastScrollbar(
state = state,
thumb = {
FastScrollbarThumb(
scrollInProgress = scrollInProgress,
interactionSource = interactionSource,
orientation = orientation,
)
},
onThumbDisplaced = onThumbMoved,
onThumbDisplaced = onThumbDisplaced,
)
}
@ -91,15 +88,12 @@ fun FastScrollbar(
* Its thumb disappears when the scrolling container is dormant.
* @param modifier a [Modifier] for the [Scrollbar]
* @param state the driving state for the [Scrollbar]
* @param scrollInProgress a flag indicating if the scrolling container for the scrollbar is
* currently scrolling
* @param orientation the orientation of the scrollbar
*/
@Composable
fun DecorativeScrollbar(
fun ScrollableState.DecorativeScrollbar(
modifier: Modifier = Modifier,
state: ScrollbarState,
scrollInProgress: Boolean,
orientation: Orientation,
) {
val interactionSource = remember { MutableInteractionSource() }
@ -111,7 +105,6 @@ fun DecorativeScrollbar(
thumb = {
DecorativeScrollbarThumb(
interactionSource = interactionSource,
scrollInProgress = scrollInProgress,
orientation = orientation,
)
},
@ -122,8 +115,7 @@ fun DecorativeScrollbar(
* A scrollbar thumb that is intended to also be a touch target for fast scrolling.
*/
@Composable
private fun FastScrollbarThumb(
scrollInProgress: Boolean,
private fun ScrollableState.FastScrollbarThumb(
interactionSource: InteractionSource,
orientation: Orientation,
) {
@ -137,7 +129,6 @@ private fun FastScrollbarThumb(
}
.background(
color = scrollbarThumbColor(
scrollInProgress = scrollInProgress,
interactionSource = interactionSource,
),
shape = RoundedCornerShape(16.dp),
@ -149,8 +140,7 @@ private fun FastScrollbarThumb(
* A decorative scrollbar thumb for communicating a user's position in a list solely.
*/
@Composable
private fun DecorativeScrollbarThumb(
scrollInProgress: Boolean,
private fun ScrollableState.DecorativeScrollbarThumb(
interactionSource: InteractionSource,
orientation: Orientation,
) {
@ -164,7 +154,6 @@ private fun DecorativeScrollbarThumb(
}
.background(
color = scrollbarThumbColor(
scrollInProgress = scrollInProgress,
interactionSource = interactionSource,
),
shape = RoundedCornerShape(16.dp),
@ -174,19 +163,18 @@ private fun DecorativeScrollbarThumb(
/**
* The color of the scrollbar thumb as a function of its interaction state.
* @param scrollInProgress if the scrolling container is currently scrolling
* @param interactionSource source of interactions in the scrolling container
*/
@Composable
private fun scrollbarThumbColor(
scrollInProgress: Boolean,
private fun ScrollableState.scrollbarThumbColor(
interactionSource: InteractionSource,
): Color {
var state by remember { mutableStateOf(Dormant) }
val pressed by interactionSource.collectIsPressedAsState()
val hovered by interactionSource.collectIsHoveredAsState()
val dragged by interactionSource.collectIsDraggedAsState()
val active = pressed || hovered || dragged || scrollInProgress
val active = (canScrollForward || canScrollForward) &&
(pressed || hovered || dragged || isScrollInProgress)
val color by animateColorAsState(
targetValue = when (state) {
@ -202,7 +190,7 @@ private fun scrollbarThumbColor(
LaunchedEffect(active) {
when (active) {
true -> state = Active
false -> {
false -> if (state == Active) {
state = Inactive
delay(INACTIVE_TO_DORMANT_COOL_DOWN)
state = Dormant

@ -27,36 +27,36 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
/**
* Remembers a function to react to [Scrollbar] thumb position movements for a [LazyListState]
* Remembers a function to react to [Scrollbar] thumb position displacements for a [LazyListState]
* @param itemsAvailable the amount of items in the list.
*/
@Composable
fun LazyListState.rememberThumbInteractions(
fun LazyListState.rememberFastScroller(
itemsAvailable: Int,
): (Float) -> Unit = rememberThumbInteractions(
): (Float) -> Unit = rememberFastScroller(
itemsAvailable = itemsAvailable,
scroll = ::scrollToItem,
)
/**
* Remembers a function to react to [Scrollbar] thumb position movements for a [LazyGridState]
* Remembers a function to react to [Scrollbar] thumb position displacements for a [LazyGridState]
* @param itemsAvailable the amount of items in the grid.
*/
@Composable
fun LazyGridState.rememberThumbInteractions(
fun LazyGridState.rememberFastScroller(
itemsAvailable: Int,
): (Float) -> Unit = rememberThumbInteractions(
): (Float) -> Unit = rememberFastScroller(
itemsAvailable = itemsAvailable,
scroll = ::scrollToItem,
)
/**
* Generic function to react to [Scrollbar] thumb interactions in a lazy layout.
* 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.
* @param scroll a function to be invoked when an index has been identified to scroll to.
*/
@Composable
private inline fun rememberThumbInteractions(
private inline fun rememberFastScroller(
itemsAvailable: Int,
crossinline scroll: suspend (index: Int) -> Unit,
): (Float) -> Unit {
@ -69,6 +69,6 @@ private inline fun rememberThumbInteractions(
scroll(indexToFind)
}
return remember {
{ percentage = it }
{ newPercentage -> percentage = newPercentage }
}
}

@ -20,6 +20,7 @@ import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
@ -62,7 +63,7 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.FastScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberThumbInteractions
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberFastScroller
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.scrollbarState
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalTintTheme
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
@ -120,12 +121,9 @@ internal fun BookmarksScreen(
val snackBarResult = onShowSnackbar(bookmarkRemovedMessage, undoText)
if (snackBarResult) {
undoBookmarkRemoval()
}
else {
clearUndoState()
undoBookmarkRemoval()
} else {
clearUndoState()
}
}
}
@ -142,19 +140,18 @@ internal fun BookmarksScreen(
}
when (feedState) {
Loading -> LoadingState(modifier)
is Success -> if (feedState.feed.isNotEmpty()) {
BookmarksGrid(
feedState,
removeFromBookmarks,
onNewsResourceViewed,
onTopicClick,
modifier,
)
} else {
EmptyState(modifier)
when (feedState) {
Loading -> LoadingState(modifier)
is Success -> if (feedState.feed.isNotEmpty()) {
BookmarksGrid(
feedState,
removeFromBookmarks,
onNewsResourceViewed,
onTopicClick,
modifier,
)
} else {
EmptyState(modifier)
}
}
@ -213,7 +210,7 @@ private fun BookmarksGrid(
val scrollbarState = scrollableState.scrollbarState(
itemsAvailable = itemsAvailable,
)
FastScrollbar(
scrollableState.FastScrollbar(
modifier = Modifier
.fillMaxHeight()
.windowInsetsPadding(WindowInsets.systemBars)
@ -221,8 +218,7 @@ private fun BookmarksGrid(
.align(Alignment.CenterEnd),
state = scrollbarState,
orientation = Orientation.Vertical,
scrollInProgress = scrollableState.isScrollInProgress,
onThumbMoved = scrollableState.rememberThumbInteractions(
onThumbDisplaced = scrollableState.rememberFastScroller(
itemsAvailable = itemsAvailable,
),
)

@ -93,7 +93,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconT
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaOverlayLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.DecorativeScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.FastScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberThumbInteractions
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberFastScroller
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.scrollbarState
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
@ -230,7 +230,7 @@ internal fun ForYouScreen(
)
}
}
FastScrollbar(
state.FastScrollbar(
modifier = Modifier
.fillMaxHeight()
.windowInsetsPadding(WindowInsets.systemBars)
@ -238,8 +238,7 @@ internal fun ForYouScreen(
.align(Alignment.CenterEnd),
state = scrollbarState,
orientation = Orientation.Vertical,
scrollInProgress = state.isScrollInProgress,
onThumbMoved = state.rememberThumbInteractions(
onThumbDisplaced = state.rememberFastScroller(
itemsAvailable = itemsAvailable,
),
)
@ -365,14 +364,13 @@ private fun TopicSelection(
)
}
}
DecorativeScrollbar(
lazyGridState.DecorativeScrollbar(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp)
.align(Alignment.BottomStart),
state = lazyGridState.scrollbarState(itemsAvailable = onboardingUiState.topics.size),
orientation = Orientation.Horizontal,
scrollInProgress = lazyGridState.isScrollInProgress,
)
}
}

@ -36,7 +36,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.FastScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberThumbInteractions
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberFastScroller
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.scrollbarState
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
@ -83,7 +83,7 @@ fun TopicsTabContent(
val scrollbarState = scrollableState.scrollbarState(
itemsAvailable = topics.size,
)
FastScrollbar(
scrollableState.FastScrollbar(
modifier = Modifier
.fillMaxHeight()
.windowInsetsPadding(WindowInsets.systemBars)
@ -91,8 +91,7 @@ fun TopicsTabContent(
.align(Alignment.CenterEnd),
state = scrollbarState,
orientation = Orientation.Vertical,
scrollInProgress = scrollableState.isScrollInProgress,
onThumbMoved = scrollableState.rememberThumbInteractions(
onThumbDisplaced = scrollableState.rememberFastScroller(
itemsAvailable = topics.size,
),
)

@ -17,17 +17,22 @@
package com.google.samples.apps.nowinandroid.feature.search
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.systemBars
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
@ -75,12 +80,17 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.FastScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberFastScroller
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.scrollbarState
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.ui.DevicePreviews
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Loading
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState.Success
import com.google.samples.apps.nowinandroid.core.ui.R.string
import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent
import com.google.samples.apps.nowinandroid.core.ui.newsFeed
@ -289,81 +299,102 @@ private fun SearchResultBody(
searchQuery: String = "",
) {
val state = rememberLazyGridState()
LazyVerticalGrid(
columns = Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
Box(
modifier = Modifier
.fillMaxSize()
.testTag("search:newsResources"),
state = state,
.fillMaxSize(),
) {
if (topics.isNotEmpty()) {
item(
span = {
GridItemSpan(maxLineSpan)
},
) {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.topics))
}
LazyVerticalGrid(
columns = Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = Modifier
.fillMaxSize()
.testTag("search:newsResources"),
state = state,
) {
if (topics.isNotEmpty()) {
item(
span = {
GridItemSpan(maxLineSpan)
},
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
) {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.topics))
}
},
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
topics.forEach { followableTopic ->
val topicId = followableTopic.topic.id
item(
key = "topic-$topicId", // Append a prefix to distinguish a key for news resources
span = {
GridItemSpan(maxLineSpan)
},
) {
InterestsItem(
name = followableTopic.topic.name,
following = followableTopic.isFollowed,
description = followableTopic.topic.shortDescription,
topicImageUrl = followableTopic.topic.imageUrl,
onClick = {
// Pass the current search query to ViewModel to save it as recent searches
onSearchTriggered(searchQuery)
onTopicClick(topicId)
},
onFollowButtonClick = { onFollowButtonClick(topicId, it) },
)
}
}
}
topics.forEach { followableTopic ->
val topicId = followableTopic.topic.id
if (newsResources.isNotEmpty()) {
item(
key = "topic-$topicId", // Append a prefix to distinguish a key for news resources
span = {
GridItemSpan(maxLineSpan)
},
) {
InterestsItem(
name = followableTopic.topic.name,
following = followableTopic.isFollowed,
description = followableTopic.topic.shortDescription,
topicImageUrl = followableTopic.topic.imageUrl,
onClick = {
// Pass the current search query to ViewModel to save it as recent searches
onSearchTriggered(searchQuery)
onTopicClick(topicId)
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.updates))
}
},
onFollowButtonClick = { onFollowButtonClick(topicId, it) },
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
}
}
if (newsResources.isNotEmpty()) {
item(
span = {
GridItemSpan(maxLineSpan)
},
) {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.updates))
}
newsFeed(
feedState = Success(feed = newsResources),
onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged,
onNewsResourceViewed = onNewsResourceViewed,
onTopicClick = onTopicClick,
onExpandedCardClick = {
onSearchTriggered(searchQuery)
},
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
newsFeed(
feedState = NewsFeedUiState.Success(feed = newsResources),
onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged,
onNewsResourceViewed = onNewsResourceViewed,
onTopicClick = onTopicClick,
onExpandedCardClick = {
onSearchTriggered(searchQuery)
},
)
}
val itemsAvailable = topics.size + newsResources.size
val scrollbarState = state.scrollbarState(
itemsAvailable = itemsAvailable,
)
state.FastScrollbar(
modifier = Modifier
.fillMaxHeight()
.windowInsetsPadding(WindowInsets.systemBars)
.padding(horizontal = 2.dp)
.align(Alignment.CenterEnd),
state = scrollbarState,
orientation = Orientation.Vertical,
onThumbDisplaced = state.rememberFastScroller(
itemsAvailable = itemsAvailable,
),
)
}
}

@ -55,7 +55,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackg
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilterChip
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.FastScrollbar
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberThumbInteractions
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.rememberFastScroller
import com.google.samples.apps.nowinandroid.core.designsystem.component.scrollbar.scrollbarState
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
@ -151,7 +151,7 @@ internal fun TopicScreen(
val scrollbarState = state.scrollbarState(
itemsAvailable = itemsAvailable,
)
FastScrollbar(
state.FastScrollbar(
modifier = Modifier
.fillMaxHeight()
.windowInsetsPadding(WindowInsets.systemBars)
@ -159,8 +159,7 @@ internal fun TopicScreen(
.align(Alignment.CenterEnd),
state = scrollbarState,
orientation = Orientation.Vertical,
scrollInProgress = state.isScrollInProgress,
onThumbMoved = state.rememberThumbInteractions(
onThumbDisplaced = state.rememberFastScroller(
itemsAvailable = itemsAvailable,
),
)

Loading…
Cancel
Save