Add a dedicated screen showing all categories

This commit is contained in:
Torsten Grote
2026-04-21 16:11:27 -03:00
parent ce6f2380cc
commit ad4da58d60
5 changed files with 136 additions and 1 deletions

View File

@@ -16,6 +16,8 @@ import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import org.fdroid.ui.apps.myAppsEntry
import org.fdroid.ui.categories.Categories
import org.fdroid.ui.categories.CategoriesViewModel
import org.fdroid.ui.details.NoAppSelected
import org.fdroid.ui.details.appDetailsEntry
import org.fdroid.ui.discover.discoverEntry
@@ -78,6 +80,14 @@ fun Main(onListeningForIntent: () -> Unit = {}) {
onSearchCleared = viewModel::onSearchCleared,
)
}
entry(NavigationKey.Categories) {
val viewModel = hiltViewModel<CategoriesViewModel>()
Categories(
categories = viewModel.categories.collectAsStateWithLifecycle(null).value,
onNav = { navKey -> navigator.navigate(navKey) },
onBackClicked = { navigator.goBack() },
)
}
entry(NavigationKey.Settings) {
val viewModel = hiltViewModel<SettingsViewModel>()
Settings(

View File

@@ -0,0 +1,94 @@
package org.fdroid.ui.categories
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults.enterAlwaysScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavKey
import org.fdroid.R
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.utils.BackButton
import org.fdroid.ui.utils.BigLoadingIndicator
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun Categories(
categories: List<CategoryItem>?,
onNav: (NavKey) -> Unit,
onBackClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
val scrollBehavior = enterAlwaysScrollBehavior(rememberTopAppBarState())
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = stringResource(R.string.main_menu__categories),
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
)
},
navigationIcon = { BackButton(onClick = onBackClicked) },
scrollBehavior = scrollBehavior,
)
},
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { paddingValues ->
val listState = rememberSaveable(saver = LazyListState.Saver) { LazyListState() }
if (categories == null) {
BigLoadingIndicator(modifier.padding(paddingValues))
} else {
LazyColumn(
state = listState,
contentPadding = paddingValues,
verticalArrangement = spacedBy(8.dp),
) {
items(items = categories, key = { it.id }, contentType = { "C" }) { categoryItem ->
CategoryRow(categoryItem, onNav)
}
}
}
}
}
@Composable
@PreviewLightDark
private fun PreviewLoading() {
FDroidContent {
Categories(null, {}, {})
}
}
@Composable
@PreviewLightDark
private fun Preview() {
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"),
)
Categories(categories, {}, {})
}
}

View File

@@ -0,0 +1,28 @@
package org.fdroid.ui.categories
import android.app.Application
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.asFlow
import dagger.hilt.android.lifecycle.HiltViewModel
import java.text.Collator
import java.util.Locale
import javax.inject.Inject
import kotlinx.coroutines.flow.map
import org.fdroid.database.FDroidDatabase
@HiltViewModel
class CategoriesViewModel @Inject constructor(app: Application, db: FDroidDatabase) :
AndroidViewModel(app) {
private val collator = Collator.getInstance(Locale.getDefault())
private val localeList = LocaleListCompat.getDefault()
val categories =
db.getRepositoryDao().getLiveCategories().asFlow().map { categories ->
categories
.map { category ->
CategoryItem(id = category.id, name = category.getName(localeList) ?: "Unknown Category")
}
.sortedWith { c1, c2 -> collator.compare(c1.name, c2.name) }
}
}

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.sp
import androidx.navigation3.runtime.NavKey
import org.fdroid.R
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.navigation.NavigationKey
@Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -29,7 +30,7 @@ fun CategoryList(
onNav: (NavKey) -> Unit,
modifier: Modifier = Modifier,
) {
val onAllCategories = { /* TODO */ }
val onAllCategories = { onNav(NavigationKey.Categories) }
AnimatedVisibility(!categoryList.isNullOrEmpty()) {
Column(modifier = modifier) {
Row(

View File

@@ -39,6 +39,8 @@ sealed interface NavigationKey : NavKey {
@Serializable data class AppList(val type: AppListType) : NavigationKey
@Serializable data object Categories : NavigationKey
@Serializable data object Repos : NavigationKey
@Serializable data class RepoDetails(val repoId: Long) : NavigationKey