From e52d2ae1f1361adcaa9256c07e739c2bc8ff6488 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 23 Feb 2026 22:43:16 +1100 Subject: [PATCH] Consistent chip layout throughout. Anywhere that CategoryChip's or FilterChip's are used, we now wrap them in a common ChipFlowRow. Its job is to ensure the vertical and horizontal spacing is suitable. Chips inside a ChipFlowRow should ideally be set to a height of chipHeight to match other chips. However, there is not a nice way to force this so we politely ask devs to use this constant and set the height. If further padding is required then it should be specified on the ChipFlowRow as per usual. Examples of when this would be valuable would be when indenting a list of categories to separate from other groups of categories. --- .../org/fdroid/ui/categories/CategoryChip.kt | 43 ++-------- .../org/fdroid/ui/categories/CategoryList.kt | 8 +- .../org/fdroid/ui/categories/ChipFlowRow.kt | 78 +++++++++++++++++++ .../org/fdroid/ui/details/AppDetails.kt | 4 +- .../kotlin/org/fdroid/ui/lists/AppsFilter.kt | 21 ++--- .../org/fdroid/ui/search/SearchResults.kt | 4 +- .../org/fdroid/ui/utils/PreviewUtils.kt | 3 + 7 files changed, 105 insertions(+), 56 deletions(-) create mode 100644 app/src/main/kotlin/org/fdroid/ui/categories/ChipFlowRow.kt diff --git a/app/src/main/kotlin/org/fdroid/ui/categories/CategoryChip.kt b/app/src/main/kotlin/org/fdroid/ui/categories/CategoryChip.kt index e657f23de..f0fb82048 100644 --- a/app/src/main/kotlin/org/fdroid/ui/categories/CategoryChip.kt +++ b/app/src/main/kotlin/org/fdroid/ui/categories/CategoryChip.kt @@ -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 = {}) - } - } - } -} diff --git a/app/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt b/app/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt index 36e7c2512..a1ab719a4 100644 --- a/app/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt +++ b/app/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt @@ -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( diff --git a/app/src/main/kotlin/org/fdroid/ui/categories/ChipFlowRow.kt b/app/src/main/kotlin/org/fdroid/ui/categories/ChipFlowRow.kt new file mode 100644 index 000000000..7ea9363c6 --- /dev/null +++ b/app/src/main/kotlin/org/fdroid/ui/categories/ChipFlowRow.kt @@ -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, {}) + } + } + } +} diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt index e87c9c2e5..9479d2c60 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt @@ -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) diff --git a/app/src/main/kotlin/org/fdroid/ui/lists/AppsFilter.kt b/app/src/main/kotlin/org/fdroid/ui/lists/AppsFilter.kt index a3fbde1d8..55664e0ce 100644 --- a/app/src/main/kotlin/org/fdroid/ui/lists/AppsFilter.kt +++ b/app/src/main/kotlin/org/fdroid/ui/lists/AppsFilter.kt @@ -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( diff --git a/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt b/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt index a86433c27..550998f59 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/SearchResults.kt @@ -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, 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) diff --git a/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt b/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt index 0a376f2f9..3735f89ac 100644 --- a/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt +++ b/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt @@ -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(