From 7909ec46d56c6fe3d81be2ca5d082d758bf42276 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 15 Sep 2025 16:13:55 -0300 Subject: [PATCH] group categories on discover screen in order to make them easier to parse/browse through --- .../org/fdroid/ui/categories/CategoryGroup.kt | 22 +++++++ .../org/fdroid/ui/categories/CategoryItem.kt | 63 +++++++++++++++++++ .../org/fdroid/ui/categories/CategoryList.kt | 57 +++++++++++------ .../kotlin/org/fdroid/ui/discover/Discover.kt | 5 +- .../fdroid/ui/discover/DiscoverPresenter.kt | 5 +- .../fdroid/ui/discover/DiscoverViewModel.kt | 2 +- next/src/main/res/values/strings-next.xml | 10 +++ 7 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 next/src/main/kotlin/org/fdroid/ui/categories/CategoryGroup.kt diff --git a/next/src/main/kotlin/org/fdroid/ui/categories/CategoryGroup.kt b/next/src/main/kotlin/org/fdroid/ui/categories/CategoryGroup.kt new file mode 100644 index 000000000..6e8c8eaf6 --- /dev/null +++ b/next/src/main/kotlin/org/fdroid/ui/categories/CategoryGroup.kt @@ -0,0 +1,22 @@ +package org.fdroid.ui.categories + +import androidx.annotation.StringRes +import org.fdroid.next.R + +data class CategoryGroup( + val id: String, + @get:StringRes + val name: Int, +) + +object CategoryGroups { + val productivity = CategoryGroup("productivity", R.string.category_group_productivity) + val tools = CategoryGroup("tools", R.string.category_group_tools) + val wallets = CategoryGroup("wallets", R.string.category_group_wallets) + val media = CategoryGroup("media", R.string.category_group_media) + val communication = CategoryGroup("communication", R.string.category_group_communication) + val device = CategoryGroup("device", R.string.category_group_device) + val storage = CategoryGroup("storage", R.string.category_group_storage) + val interests = CategoryGroup("interests", R.string.category_group_interests) + val misc = CategoryGroup("misc", R.string.category_group_misc) +} diff --git a/next/src/main/kotlin/org/fdroid/ui/categories/CategoryItem.kt b/next/src/main/kotlin/org/fdroid/ui/categories/CategoryItem.kt index 86f67f68e..2829250d1 100644 --- a/next/src/main/kotlin/org/fdroid/ui/categories/CategoryItem.kt +++ b/next/src/main/kotlin/org/fdroid/ui/categories/CategoryItem.kt @@ -126,4 +126,67 @@ data class CategoryItem(val id: String, val name: String) { "Writing" -> Icons.Default.EditNote else -> Icons.Default.Category } + val group: CategoryGroup + get() = when (id) { + "App Store & Updater" -> CategoryGroups.device + "Bookmark" -> CategoryGroups.storage + "Browser" -> CategoryGroups.productivity + "Calculator" -> CategoryGroups.tools + "Calendar & Agenda" -> CategoryGroups.productivity + "Cloud Storage & File Sync" -> CategoryGroups.storage + "Connectivity" -> CategoryGroups.device + "Development" -> CategoryGroups.interests + "DNS & Hosts" -> CategoryGroups.device + "Draw" -> CategoryGroups.interests + "Ebook Reader" -> CategoryGroups.media + "Email" -> CategoryGroups.communication + "File Encryption & Vault" -> CategoryGroups.storage + "File Transfer" -> CategoryGroups.storage + "Finance Manager" -> CategoryGroups.wallets + "Forum" -> CategoryGroups.communication + "Gallery" -> CategoryGroups.storage + "Games" -> CategoryGroups.media + "Graphics" -> CategoryGroups.interests + "Habit Tracker" -> CategoryGroups.productivity + "Icon Pack" -> CategoryGroups.device + "Internet" -> CategoryGroups.productivity + "Keyboard & IME" -> CategoryGroups.device + "Launcher" -> CategoryGroups.device + "Local Media Player" -> CategoryGroups.media + "Messaging" -> CategoryGroups.communication + "Money" -> CategoryGroups.wallets + "Multimedia" -> CategoryGroups.media + "Music Practice Tool" -> CategoryGroups.interests + "Navigation" -> CategoryGroups.tools + "News" -> CategoryGroups.interests + "Note" -> CategoryGroups.storage + "Online Media Player" -> CategoryGroups.media + "Pass Wallet" -> CategoryGroups.wallets + "Password & 2FA" -> CategoryGroups.device + "Phone & SMS" -> CategoryGroups.communication + "Podcast" -> CategoryGroups.media + "Public Transport" -> CategoryGroups.tools + "Reading" -> CategoryGroups.media + "Recipe Manager" -> CategoryGroups.interests + "Science & Education" -> CategoryGroups.interests + "Security" -> CategoryGroups.device + "Shopping List" -> CategoryGroups.tools + "Social Network" -> CategoryGroups.communication + "Sports & Health" -> CategoryGroups.interests + "System" -> CategoryGroups.device + "Task" -> CategoryGroups.productivity + "Text Editor" -> CategoryGroups.productivity + "Theming" -> CategoryGroups.device + "Time" -> CategoryGroups.productivity + "Translation & Dictionary" -> CategoryGroups.tools + "Voice & Video Chat" -> CategoryGroups.communication + "Unit Convertor" -> CategoryGroups.tools + "VPN & Proxy" -> CategoryGroups.device + "Wallet" -> CategoryGroups.wallets + "Wallpaper" -> CategoryGroups.device + "Weather" -> CategoryGroups.tools + "Workout" -> CategoryGroups.interests + "Writing" -> CategoryGroups.productivity + else -> CategoryGroups.misc + } } diff --git a/next/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt b/next/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt index d560cc4bb..2c7a95a00 100644 --- a/next/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt +++ b/next/src/main/kotlin/org/fdroid/ui/categories/CategoryList.kt @@ -7,7 +7,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -19,11 +21,11 @@ import org.fdroid.ui.lists.AppListType @Composable fun CategoryList( - categories: List?, + categoryMap: Map>?, onNav: (NavKey) -> Unit, modifier: Modifier = Modifier ) { - if (categories != null) Column( + if (categoryMap != null) Column( modifier = modifier ) { Text( @@ -31,15 +33,28 @@ fun CategoryList( style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 8.dp, start = 4.dp), ) - FlowRow( - horizontalArrangement = Arrangement.Start, - ) { - categories.forEach { category -> - CategoryChip(category, { - val type = AppListType.Category(category.name, category.id) - val navKey = NavigationKey.AppList(type) - onNav(navKey) - }) + // we'll sort the groups here, because before we didn't have the context to get names + val context = LocalContext.current + val sortedMap = remember(categoryMap) { + val comparator = compareBy { context.getString(it.name) } + categoryMap.toSortedMap(comparator) + } + sortedMap.forEach { (group, categories) -> + Text( + text = stringResource(group.name), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(4.dp), + ) + FlowRow( + horizontalArrangement = Arrangement.Start, + ) { + categories.forEach { category -> + CategoryChip(category, { + val type = AppListType.Category(category.name, category.id) + val navKey = NavigationKey.AppList(type) + onNav(navKey) + }) + } } } } @@ -49,14 +64,18 @@ fun CategoryList( @Composable fun CategoryListPreview() { FDroidContent { - val categories = listOf( - CategoryItem("App Store & Updater", "App Store & Updater"), - CategoryItem("Browser", "Browser"), - CategoryItem("Calendar & Agenda", "Calendar & Agenda"), - CategoryItem("Cloud Storage & File Sync", "Cloud Storage & File Sync"), - CategoryItem("Connectivity", "Connectivity"), - CategoryItem("Development", "Development"), - CategoryItem("doesn't exist", "Foo bar"), + val categories = mapOf( + CategoryGroups.productivity to listOf( + CategoryItem("App Store & Updater", "App Store & Updater"), + CategoryItem("Browser", "Browser"), + CategoryItem("Calendar & Agenda", "Calendar & Agenda"), + ), + CategoryGroups.media to listOf( + CategoryItem("Cloud Storage & File Sync", "Cloud Storage & File Sync"), + CategoryItem("Connectivity", "Connectivity"), + CategoryItem("Development", "Development"), + CategoryItem("doesn't exist", "Foo bar"), + ) ) CategoryList(categories, {}) } diff --git a/next/src/main/kotlin/org/fdroid/ui/discover/Discover.kt b/next/src/main/kotlin/org/fdroid/ui/discover/Discover.kt index c4c38dde5..ab12c9f65 100644 --- a/next/src/main/kotlin/org/fdroid/ui/discover/Discover.kt +++ b/next/src/main/kotlin/org/fdroid/ui/discover/Discover.kt @@ -171,10 +171,11 @@ fun Discover( } AnimatedVisibility(discoverModel is LoadedDiscoverModel) { CategoryList( - categories = (discoverModel as LoadedDiscoverModel).categories, + categoryMap = (discoverModel as LoadedDiscoverModel).categories, onNav = onNav, modifier = Modifier - .padding(16.dp) + .padding(horizontal = 16.dp) + .padding(bottom = 16.dp) .fillMaxWidth() ) } diff --git a/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverPresenter.kt b/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverPresenter.kt index 5316e3548..20064ace8 100644 --- a/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverPresenter.kt +++ b/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverPresenter.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.collectAsState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import org.fdroid.database.Repository +import org.fdroid.ui.categories.CategoryGroup import org.fdroid.ui.categories.CategoryItem @Composable @@ -34,7 +35,7 @@ fun DiscoverPresenter( LoadedDiscoverModel( newApps = apps.filter { it.isNew }, recentlyUpdatedApps = apps.filter { !it.isNew }, - categories = categories, + categories = categories?.groupBy { it.group }, searchResults = searchResults, ) } @@ -46,6 +47,6 @@ object NoEnabledReposDiscoverModel : DiscoverModel() data class LoadedDiscoverModel( val newApps: List, val recentlyUpdatedApps: List, - val categories: List?, + val categories: Map>?, val searchResults: SearchResults? = null, ) : DiscoverModel() diff --git a/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt b/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt index e083e0905..ef9bac3ec 100644 --- a/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt +++ b/next/src/main/kotlin/org/fdroid/ui/discover/DiscoverViewModel.kt @@ -61,7 +61,7 @@ class DiscoverViewModel @Inject constructor( ) } } - val categories = db.getRepositoryDao().getLiveCategories().asFlow().map { categories -> + private val categories = db.getRepositoryDao().getLiveCategories().asFlow().map { categories -> categories.map { category -> CategoryItem( id = category.id, diff --git a/next/src/main/res/values/strings-next.xml b/next/src/main/res/values/strings-next.xml index 331b30dab..76f709f0f 100644 --- a/next/src/main/res/values/strings-next.xml +++ b/next/src/main/res/values/strings-next.xml @@ -36,6 +36,16 @@ selected Category + Productivity + Tools + Digital Wallets + Entertainment & Media + Communication + Device + Files & Storage + Interests + Miscellaneous + By %1$s Last updated: %1$s