Skip to content

Commit

Permalink
Improve the StableList API
Browse files Browse the repository at this point in the history
Use delegation to simplify call sites
  • Loading branch information
fibelatti committed Nov 22, 2023
1 parent fa4e3bf commit 8e4ef35
Show file tree
Hide file tree
Showing 12 changed files with 45 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private fun <T> SelectionDialogContent(
)
}

items(options.value) { option ->
items(options) { option ->
FilledTonalButton(
onClick = { onOptionSelected(option) },
modifier = Modifier.fillMaxWidth(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ private fun MenuItemsContent(
onMenuItemClick: (MainState.MenuItemComponent, data: Any?) -> Unit,
) {
AnimatedContent(
targetState = menuItems.value,
targetState = menuItems,
label = "MenuItemsContent",
) { items ->
Row {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private fun SavedFiltersScreen(
onSavedFilterClicked: (SavedFilter) -> Unit,
onSavedFilterLongClicked: (SavedFilter) -> Unit,
) {
if (savedFilters.value.isEmpty()) {
if (savedFilters.isEmpty()) {
EmptyListContent(
icon = painterResource(id = R.drawable.ic_filter),
title = stringResource(id = R.string.saved_filters_empty_title),
Expand All @@ -121,7 +121,7 @@ private fun SavedFiltersScreen(
.asPaddingValues(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(savedFilters.value) { savedFilter ->
items(savedFilters) { savedFilter ->
SavedFilterItem(
savedFilter = savedFilter,
onClicked = onSavedFilterClicked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import com.fibelatti.ui.components.ToggleButtonGroup
import com.fibelatti.ui.foundation.StableList
import com.fibelatti.ui.foundation.asHorizontalPaddingDp
import com.fibelatti.ui.foundation.navigationBarsCompat
import com.fibelatti.ui.foundation.stableListOf
import com.fibelatti.ui.foundation.toStableList
import com.fibelatti.ui.preview.ThemePreviews
import com.fibelatti.ui.theme.ExtendedTheme
Expand Down Expand Up @@ -162,7 +163,7 @@ private fun NoteListContent(
listState.scrollToItem(index = 0)
}

if (notes.value.isEmpty()) {
if (notes.isEmpty()) {
EmptyListContent(
icon = painterResource(id = R.drawable.ic_notes),
title = stringResource(id = R.string.notes_empty_title),
Expand Down Expand Up @@ -209,7 +210,7 @@ private fun NoteListContent(
bottom = 100.dp,
),
) {
items(notes.value) { note ->
items(notes) { note ->
NoteListItem(
note = note,
onNoteClicked = onNoteClicked,
Expand Down Expand Up @@ -282,7 +283,7 @@ object NoteList {
private fun EmptyNoteListScreenPreview() {
ExtendedTheme {
NoteListContent(
notes = StableList(),
notes = stableListOf(),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import com.fibelatti.pinboard.features.tags.presentation.TagManagerViewModel
import com.fibelatti.ui.foundation.StableList
import com.fibelatti.ui.foundation.imePaddingCompat
import com.fibelatti.ui.foundation.navigationBarsPaddingCompat
import com.fibelatti.ui.foundation.stableListOf
import com.fibelatti.ui.foundation.toStableList
import com.fibelatti.ui.preview.ThemePreviews
import com.fibelatti.ui.theme.ExtendedTheme
Expand Down Expand Up @@ -430,7 +431,7 @@ private fun EditBookmarkScreenPreview(
searchTagInput = "",
onSearchTagInputChanged = {},
onAddTagClicked = {},
suggestedTags = StableList(),
suggestedTags = stableListOf(),
onSuggestedTagClicked = {},
currentTagsTitle = stringResource(id = R.string.tags_added_title),
currentTags = post.tags.orEmpty().toStableList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ fun PopularBookmarksContent(
onBookmarkLongClicked: (Post) -> Unit = {},
sidePanelVisible: Boolean = false,
) {
if (posts.value.isEmpty()) {
if (posts.isEmpty()) {
EmptyListContent(
icon = painterResource(id = R.drawable.ic_pin),
title = stringResource(id = R.string.posts_empty_title),
Expand All @@ -171,7 +171,7 @@ fun PopularBookmarksContent(
bottom = 100.dp,
),
) {
items(posts.value) { bookmark ->
items(posts) { bookmark ->
PopularBookmarkItem(
post = bookmark,
onPostClicked = onBookmarkClicked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ fun SearchBookmarksScreen(
searchTerm: String = "",
onSearchTermChanged: (String) -> Unit = {},
onKeyboardSearch: () -> Unit = {},
selectedTags: StableList<Tag> = StableList(),
selectedTags: StableList<Tag> = stableListOf(),
onSelectedTagRemoved: (Tag) -> Unit = {},
availableTags: StableList<Tag> = StableList(),
availableTags: StableList<Tag> = stableListOf(),
isLoadingTags: Boolean = false,
onTagsSortOptionClicked: (TagList.Sorting) -> Unit = {},
tagsSearchTerm: String = "",
Expand Down Expand Up @@ -243,7 +243,7 @@ fun SearchBookmarksScreen(
)

AnimatedVisibility(
visible = selectedTags.value.isNotEmpty(),
visible = selectedTags.isNotEmpty(),
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Expand All @@ -261,7 +261,7 @@ fun SearchBookmarksScreen(
)

SingleLineChipGroup(
items = selectedTags.value
items = selectedTags
.map {
ChipGroup.Item(
text = it.name,
Expand All @@ -270,13 +270,13 @@ fun SearchBookmarksScreen(
}
.toStableList(),
onItemClick = { item ->
onSelectedTagRemoved(selectedTags.value.first { it.name == item.text })
onSelectedTagRemoved(selectedTags.first { it.name == item.text })
},
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
onItemIconClick = { item ->
onSelectedTagRemoved(selectedTags.value.first { it.name == item.text })
onSelectedTagRemoved(selectedTags.first { it.name == item.text })
},
contentPadding = PaddingValues(horizontal = 16.dp),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import com.fibelatti.ui.foundation.asHorizontalPaddingDp
import com.fibelatti.ui.foundation.imeCompat
import com.fibelatti.ui.foundation.navigationBarsCompat
import com.fibelatti.ui.foundation.navigationBarsPaddingCompat
import com.fibelatti.ui.foundation.stableListOf
import com.fibelatti.ui.foundation.toStableList
import com.fibelatti.ui.preview.ThemePreviews
import com.fibelatti.ui.theme.ExtendedTheme
Expand Down Expand Up @@ -206,7 +207,7 @@ fun TagList(
header()
}

if (items.value.isEmpty() && searchInput.isBlank()) {
if (items.isEmpty() && searchInput.isBlank()) {
item {
EmptyListContent(
icon = painterResource(id = R.drawable.ic_tag),
Expand All @@ -225,7 +226,7 @@ fun TagList(
)
}

items(items.value) { item ->
items(items) { item ->
TagListItem(
item = item,
onTagClicked = onTagClicked,
Expand Down Expand Up @@ -410,7 +411,7 @@ private fun EmptyTagListPreview() {
ExtendedTheme {
TagList(
header = {},
items = StableList(),
items = stableListOf(),
isLoading = false,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ fun TagManager(
)
}

if (suggestedTags.value.isNotEmpty()) {
if (suggestedTags.isNotEmpty()) {
SingleLineChipGroup(
items = remember(suggestedTags.value) {
suggestedTags.value.map { tag -> ChipGroup.Item(text = tag) }.toStableList()
items = remember(suggestedTags) {
suggestedTags.map { tag -> ChipGroup.Item(text = tag) }.toStableList()
},
onItemClick = { item -> onSuggestedTagClicked(suggestedTags.value.first { it == item.text }) },
onItemClick = { item -> onSuggestedTagClicked(suggestedTags.first { it == item.text }) },
modifier = Modifier
.constrainAs(clSuggestedTags) {
start.linkTo(parent.start)
Expand Down Expand Up @@ -166,7 +166,7 @@ fun TagManager(
.constrainAs(clCurrentTagsTitle) {
start.linkTo(parent.start, margin = horizontalPadding)
top.linkTo(
anchor = if (suggestedTags.value.isNotEmpty()) clDivider.bottom else clAddTagInput.bottom,
anchor = if (suggestedTags.isNotEmpty()) clDivider.bottom else clAddTagInput.bottom,
margin = 16.dp,
)
end.linkTo(parent.end, margin = horizontalPadding)
Expand All @@ -179,8 +179,8 @@ fun TagManager(
val closeIcon = painterResource(id = R.drawable.ic_close)

MultilineChipGroup(
items = remember(currentTags.value) {
currentTags.value
items = remember(currentTags) {
currentTags
.map { tag -> ChipGroup.Item(text = tag.name, icon = closeIcon) }
.toStableList()
},
Expand All @@ -192,7 +192,7 @@ fun TagManager(
width = Dimension.fillToConstraints
},
itemTonalElevation = 16.dp,
onItemIconClick = { item -> onRemoveCurrentTagClicked(currentTags.value.first { it.name == item.text }) },
onItemIconClick = { item -> onRemoveCurrentTagClicked(currentTags.first { it.name == item.text }) },
itemColors = ChipGroup.colors(
unselectedTextColor = MaterialTheme.colorScheme.onPrimaryContainer,
unselectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ fun ColumnToggleButtonGroup(
) {
Column(modifier = modifier) {
val mode = when {
items.value.all { it.text != "" && it.icon == EmptyPainter } -> ToggleButtonGroup.Mode.TextOnly
items.value.all { it.text == "" && it.icon != EmptyPainter } -> ToggleButtonGroup.Mode.IconOnly
items.all { it.text != "" && it.icon == EmptyPainter } -> ToggleButtonGroup.Mode.TextOnly
items.all { it.text == "" && it.icon != EmptyPainter } -> ToggleButtonGroup.Mode.IconOnly
else -> ToggleButtonGroup.Mode.TextAndIcon
}

items.value.forEachIndexed { index, toggleButtonGroupItem ->
items.forEachIndexed { index, toggleButtonGroupItem ->
val isButtonSelected = selectedIndex == index

ToggleButton(
Expand All @@ -71,7 +71,7 @@ fun ColumnToggleButtonGroup(
.offset(y = borderSize * -index),
buttonShape = when (index) {
0 -> shape.copy(bottomStart = SquareCorner, bottomEnd = SquareCorner)
items.value.size - 1 -> shape.copy(topStart = SquareCorner, topEnd = SquareCorner)
items.size - 1 -> shape.copy(topStart = SquareCorner, topEnd = SquareCorner)
else -> shape.copy(all = SquareCorner)
},
border = border,
Expand Down Expand Up @@ -107,12 +107,12 @@ fun RowToggleButtonGroup(
Row(modifier = modifier) {
val squareCorner = CornerSize(0.dp)
val mode = when {
items.value.all { it.text != "" && it.icon == EmptyPainter } -> ToggleButtonGroup.Mode.TextOnly
items.value.all { it.text == "" && it.icon != EmptyPainter } -> ToggleButtonGroup.Mode.IconOnly
items.all { it.text != "" && it.icon == EmptyPainter } -> ToggleButtonGroup.Mode.TextOnly
items.all { it.text == "" && it.icon != EmptyPainter } -> ToggleButtonGroup.Mode.IconOnly
else -> ToggleButtonGroup.Mode.TextOnly
}

items.value.forEachIndexed { index, toggleButtonGroupItem ->
items.forEachIndexed { index, toggleButtonGroupItem ->
val isButtonSelected = selectedIndex == index

ToggleButton(
Expand All @@ -124,7 +124,7 @@ fun RowToggleButtonGroup(
.offset(x = borderSize * -index),
buttonShape = when (index) {
0 -> shape.copy(bottomEnd = squareCorner, topEnd = squareCorner)
items.value.size - 1 -> shape.copy(topStart = squareCorner, bottomStart = squareCorner)
items.size - 1 -> shape.copy(topStart = squareCorner, bottomStart = squareCorner)
else -> shape.copy(all = squareCorner)
},
border = border,
Expand Down
4 changes: 2 additions & 2 deletions ui/src/main/java/com/fibelatti/ui/components/ChipGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fun MultilineChipGroup(
) {
Layout(
content = {
items.value.forEach { item ->
items.forEach { item ->
Chip(
item = item,
onClick = onItemClick,
Expand Down Expand Up @@ -154,7 +154,7 @@ fun SingleLineChipGroup(
contentPadding = contentPadding,
horizontalArrangement = Arrangement.spacedBy(spacing),
) {
items(items.value) { item ->
items(items) { item ->
Chip(
item = item,
onClick = onItemClick,
Expand Down
11 changes: 5 additions & 6 deletions ui/src/main/java/com/fibelatti/ui/foundation/StableList.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package com.fibelatti.ui.foundation

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable

/**
* An [Immutable] class that wraps a [List] of [T] in order to provide stability to a composable
* when needed. The given [value] is assumed to be immutable.
* when needed. The given `value` is assumed to be immutable.
*/
@Stable
data class StableList<T>(val value: List<T> = emptyList())
@Immutable
class StableList<T> internal constructor(value: List<T>) : List<T> by value

fun <T> stableListOf(vararg items: T): StableList<T> = StableList(items.toList())
fun <T> stableListOf(vararg items: T): StableList<T> = StableList(value = items.toList())

fun <T> List<T>.toStableList(): StableList<T> = StableList(value = toList())

fun <T> Array<T>.toStableList(): StableList<T> = toList().toStableList()
fun <T> Array<T>.toStableList(): StableList<T> = StableList(value = toList())

0 comments on commit 8e4ef35

Please sign in to comment.