Merge branch 'ux-feedback--chip-flow-row' into 'master'

Consistent chip layout throughout.

See merge request fdroid/fdroidclient!1628
This commit is contained in:
Torsten Grote
2026-02-24 19:19:57 +00:00
7 changed files with 105 additions and 56 deletions

View File

@@ -2,7 +2,6 @@ package org.fdroid.ui.categories
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@@ -23,8 +22,6 @@ import androidx.compose.ui.unit.dp
import org.fdroid.R
import org.fdroid.ui.FDroidContent
private val chipHeight = 36.dp
@Composable
fun CategoryChip(
categoryItem: CategoryItem,
@@ -53,7 +50,7 @@ fun CategoryChip(
)
},
selected = selected,
modifier = modifier.padding(horizontal = 4.dp).height(chipHeight)
modifier = modifier.height(chipHeight)
)
}
@@ -80,7 +77,7 @@ fun CategoryChip(
overflow = TextOverflow.Ellipsis,
)
},
modifier = modifier.padding(horizontal = 4.dp).height(chipHeight)
modifier = modifier.height(chipHeight)
)
}
@@ -88,7 +85,10 @@ fun CategoryChip(
@Composable
fun CategoryCardPreview() {
FDroidContent {
Column {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(8.dp)
) {
CategoryChip(
CategoryItem("VPN & Proxy", "VPN & Proxy"),
selected = true,
@@ -106,34 +106,3 @@ fun CategoryCardPreview() {
}
}
}
/**
* More similar to how multiple category chips are shown in the main category list.
* Used to show spacing between items, specifically with regards to how Android specifies
* a minimum height for interactive elements. This leads to the conclusion that if we make
* the chips too short, we get an artificially large vertical gap. Hence why we set
* the height explicitly on category chips to make them align with this minimum height.
*/
@Preview
@Composable
fun CategoryCardFlowRowPreview() {
val categories = listOf(
CategoryItem("Cloud Storage & File Sync", "Cloud Storage & File Sync"),
CategoryItem("Connectivity", "Connectivity"),
CategoryItem("Development", "Development"),
CategoryItem("doesn't exist", "Foo bar"),
)
FDroidContent {
FlowRow(
horizontalArrangement = Arrangement.Start,
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(24.dp, 8.dp, 4.dp, 20.dp)
) {
categories.map { category ->
CategoryChip(category, onClick = {})
}
}
}
}

View File

@@ -1,8 +1,6 @@
package org.fdroid.ui.categories
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
@@ -48,11 +46,9 @@ fun CategoryList(
.fillMaxWidth()
.padding(16.dp, 2.dp),
)
FlowRow(
horizontalArrangement = Arrangement.Start,
verticalArrangement = Arrangement.spacedBy(8.dp),
ChipFlowRow(
modifier = Modifier
.padding(24.dp, 8.dp, 4.dp, 20.dp)
.padding(start = 16.dp, bottom = 12.dp)
) {
categories.forEach { category ->
CategoryChip(

View File

@@ -0,0 +1,78 @@
package org.fdroid.ui.categories
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.FlowRowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.fdroid.ui.FDroidContent
val chipHeight = 36.dp
/**
* When presenting a list of chips (e.g. categories, repositories, sort criteria),
* use this to ensure appropriate spacing between elements. Make sure to set the
* height of your chips to [chipHeight] so that all chips match.
*/
@Composable
fun ChipFlowRow(
modifier: Modifier = Modifier,
content: @Composable FlowRowScope.() -> Unit,
) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.padding(8.dp),
content = content,
)
}
@Preview
@Composable
fun ChipFlowRowFewItemsPreview() {
val categories = listOf(
CategoryItem("News", "News"),
CategoryItem("Note", "Note"),
CategoryItem("doesn't exist", "Oops"),
)
FDroidContent {
ChipFlowRow {
categories.map { category ->
CategoryChip(category, {})
}
}
}
}
@Preview
@Composable
fun ChipFlowRowManyItemsPreview() {
val categories = listOf(
CategoryItem("Cloud Storage & File Sync", "Cloud Storage & File Sync"),
CategoryItem("Connectivity", "Connectivity"),
CategoryItem("Development", "Development"),
CategoryItem("doesn't exist", "Foo bar"),
CategoryItem("Online Media Player", "Online Media Player"),
CategoryItem("Pass Wallet", "Pass Wallet"),
CategoryItem("Password & 2FA", "Password & 2FA"),
CategoryItem("Phone & SMS", "Phone & SMS"),
CategoryItem("Podcast", "Podcast"),
CategoryItem("Public Transport", "Public Transport"),
CategoryItem("Reading", "Reading"),
CategoryItem("Recipe Manager", "Recipe Manager"),
CategoryItem("Religion", "Religion"),
CategoryItem("Science & Education", "Science & Education"),
)
FDroidContent {
ChipFlowRow {
categories.map { category ->
CategoryChip(category, {})
}
}
}
}

View File

@@ -3,7 +3,6 @@ package org.fdroid.ui.details
import android.util.Log
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
@@ -69,6 +68,7 @@ import org.fdroid.R
import org.fdroid.install.InstallState
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.categories.CategoryChip
import org.fdroid.ui.categories.ChipFlowRow
import org.fdroid.ui.icons.License
import org.fdroid.ui.icons.Litecoin
import org.fdroid.ui.lists.AppListType
@@ -378,7 +378,7 @@ fun AppDetails(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
initiallyExpanded = true,
) {
FlowRow(modifier = Modifier.padding(start = 16.dp)) {
ChipFlowRow(modifier = Modifier.padding(start = 8.dp)) {
item.categories.forEach { item ->
CategoryChip(item, onClick = {
val categoryNav = AppListType.Category(item.name, item.id)

View File

@@ -2,10 +2,10 @@ package org.fdroid.ui.lists
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -40,7 +40,9 @@ import org.fdroid.R
import org.fdroid.database.AppListSortOrder
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.categories.CategoryChip
import org.fdroid.ui.categories.ChipFlowRow
import org.fdroid.ui.categories.CategoryItem
import org.fdroid.ui.categories.chipHeight
import org.fdroid.ui.icons.PackageVariant
import org.fdroid.ui.utils.AsyncShimmerImage
import org.fdroid.ui.utils.getAppListInfo
@@ -58,15 +60,13 @@ fun AppsFilter(
icon = Icons.AutoMirrored.Default.Sort,
text = stringResource(R.string.sort_title),
)
FlowRow(
horizontalArrangement = spacedBy(8.dp),
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
ChipFlowRow(
modifier = Modifier.padding(start = 16.dp)
) {
val byNameSelected = info.model.sortBy == AppListSortOrder.NAME
FilterChip(
selected = byNameSelected,
modifier = Modifier.height(chipHeight),
leadingIcon = {
if (byNameSelected) {
Icon(
@@ -87,6 +87,7 @@ fun AppsFilter(
val byLatestSelected = info.model.sortBy == AppListSortOrder.LAST_UPDATED
FilterChip(
selected = byLatestSelected,
modifier = Modifier.height(chipHeight),
leadingIcon = {
if (byLatestSelected) {
Icon(
@@ -106,6 +107,7 @@ fun AppsFilter(
)
FilterChip(
selected = info.model.filterIncompatible,
modifier = Modifier.height(chipHeight),
leadingIcon = {
if (info.model.filterIncompatible) {
Icon(
@@ -141,7 +143,7 @@ fun AppsFilter(
icon = Icons.Default.Category,
text = stringResource(R.string.main_menu__categories),
)
FlowRow(
ChipFlowRow(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
@@ -158,14 +160,14 @@ fun AppsFilter(
}
}
}
if (info.model.repositories.isNotEmpty()) {
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
FilterHeader(
icon = PackageVariant,
text = stringResource(R.string.app_details_repositories),
)
FlowRow(
horizontalArrangement = spacedBy(8.dp),
ChipFlowRow(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
@@ -174,6 +176,7 @@ fun AppsFilter(
val selected = repo.repoId in info.model.filteredRepositoryIds
FilterChip(
selected = selected,
modifier = Modifier.height(chipHeight),
leadingIcon = {
if (selected) {
Icon(

View File

@@ -2,7 +2,6 @@ package org.fdroid.ui.search
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -24,6 +23,7 @@ import androidx.compose.ui.unit.dp
import org.fdroid.R
import org.fdroid.ui.categories.CategoryChip
import org.fdroid.ui.categories.CategoryItem
import org.fdroid.ui.categories.ChipFlowRow
import org.fdroid.ui.lists.AppListItem
import org.fdroid.ui.lists.AppListRow
import org.fdroid.ui.lists.AppListType
@@ -135,7 +135,7 @@ private fun CategoriesFlowRow(categories: List<CategoryItem>, onNav: (Navigation
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(8.dp)
)
FlowRow {
ChipFlowRow {
categories.forEach { item ->
CategoryChip(categoryItem = item, onClick = {
val type = AppListType.Category(item.name, item.id)

View File

@@ -167,6 +167,9 @@ val testApp = AppDetailsItem(
categories = listOf(
CategoryItem("Multimedia", "Multimedia"),
CategoryItem("Internet", "Internet"),
CategoryItem("Cloud Storage & File Sync", "Cloud Storage & File Sync"),
CategoryItem("Connectivity", "Connectivity"),
CategoryItem("Development", "Development"),
),
antiFeatures = listOf(
AntiFeature(