feat(resources): compact cards, BottomSheet filter/sort (tags+format+language)
- Cards: title + author + overflow row (format, size, tags) - Description removed from card (detail-only) - SearchBar + FilterList icon (like Inventar) - BottomSheet: Tags, Format, Sprache filter + Sortierung - Crash-fix: catch exceptions in refresh()
This commit is contained in:
parent
c24a32b033
commit
fd2eae227b
2 changed files with 308 additions and 105 deletions
|
|
@ -18,20 +18,28 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.BadgedBox
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
|
|
@ -39,6 +47,7 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -49,6 +58,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
|
|
@ -64,7 +74,7 @@ internal fun ResourceListScreen(
|
|||
viewModel: ResourceListViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
var isSortMenuExpanded by remember { mutableStateOf(false) }
|
||||
var isBottomSheetVisible by remember { mutableStateOf(false) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val context = LocalContext.current
|
||||
|
||||
|
|
@ -72,14 +82,21 @@ internal fun ResourceListScreen(
|
|||
uiState.resources,
|
||||
uiState.searchQuery,
|
||||
uiState.selectedTags,
|
||||
uiState.selectedFormat,
|
||||
uiState.selectedLanguage,
|
||||
uiState.sortMode
|
||||
) {
|
||||
uiState.resources
|
||||
.filter { resource ->
|
||||
(uiState.searchQuery.isBlank() ||
|
||||
resource.title.contains(uiState.searchQuery, ignoreCase = true)) &&
|
||||
resource.title.contains(uiState.searchQuery, ignoreCase = true) ||
|
||||
resource.author?.contains(uiState.searchQuery, ignoreCase = true) == true) &&
|
||||
(uiState.selectedTags.isEmpty() ||
|
||||
resource.tags.any { it in uiState.selectedTags })
|
||||
resource.tags.any { it in uiState.selectedTags }) &&
|
||||
(uiState.selectedFormat == null ||
|
||||
resource.fileFormat == uiState.selectedFormat) &&
|
||||
(uiState.selectedLanguage == null ||
|
||||
resource.language == uiState.selectedLanguage)
|
||||
}
|
||||
.let { list ->
|
||||
when (uiState.sortMode) {
|
||||
|
|
@ -90,7 +107,7 @@ internal fun ResourceListScreen(
|
|||
}
|
||||
}
|
||||
|
||||
// Handle download state changes (open file on success, show error snackbar)
|
||||
// Handle download state changes
|
||||
uiState.downloadStates.forEach { (guid, state) ->
|
||||
when (state) {
|
||||
is DownloadState.Success -> {
|
||||
|
|
@ -137,66 +154,48 @@ internal fun ResourceListScreen(
|
|||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = uiState.searchQuery,
|
||||
onValueChange = { viewModel.setSearchQuery(it) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text("Suche nach Titel…") },
|
||||
leadingIcon = {
|
||||
Icon(Icons.Filled.Search, contentDescription = null)
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
if (uiState.allTags.isNotEmpty()) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
// Search bar + filter button (like Inventar)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = uiState.searchQuery,
|
||||
onValueChange = { viewModel.setSearchQuery(it) },
|
||||
modifier = Modifier.weight(1f),
|
||||
placeholder = { Text("Suchen…") },
|
||||
leadingIcon = {
|
||||
Icon(Icons.Filled.Search, contentDescription = null)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (uiState.searchQuery.isNotEmpty()) {
|
||||
IconButton(onClick = { viewModel.setSearchQuery("") }) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Suche löschen")
|
||||
}
|
||||
}
|
||||
},
|
||||
singleLine = true
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
BadgedBox(
|
||||
badge = {
|
||||
if (uiState.activeFilterSortCount > 0) {
|
||||
Badge { Text("${uiState.activeFilterSortCount}") }
|
||||
}
|
||||
}
|
||||
) {
|
||||
uiState.allTags.forEach { tag ->
|
||||
FilterChip(
|
||||
selected = tag in uiState.selectedTags,
|
||||
onClick = { viewModel.toggleTag(tag) },
|
||||
label = { Text(tag) }
|
||||
IconButton(onClick = { isBottomSheetVisible = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.FilterList,
|
||||
contentDescription = "Filter & Sortierung"
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "Sortierung:",
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Box {
|
||||
TextButton(onClick = { isSortMenuExpanded = true }) {
|
||||
Text(uiState.sortMode.label)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = isSortMenuExpanded,
|
||||
onDismissRequest = { isSortMenuExpanded = false }
|
||||
) {
|
||||
SortMode.entries.forEach { mode ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(mode.label) },
|
||||
onClick = {
|
||||
viewModel.setSortMode(mode)
|
||||
isSortMenuExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
when {
|
||||
uiState.isLoading && filteredResources.isEmpty() -> {
|
||||
Box(
|
||||
|
|
@ -220,7 +219,8 @@ internal fun ResourceListScreen(
|
|||
}
|
||||
else -> {
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
items(filteredResources, key = { it.guid }) { resource ->
|
||||
val downloadState = uiState.downloadStates[resource.guid] ?: DownloadState.Idle
|
||||
|
|
@ -243,6 +243,21 @@ internal fun ResourceListScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isBottomSheetVisible) {
|
||||
FilterSortBottomSheet(
|
||||
uiState = uiState,
|
||||
onTagToggled = viewModel::toggleTag,
|
||||
onFormatSelected = viewModel::setFormat,
|
||||
onLanguageSelected = viewModel::setLanguage,
|
||||
onSortSelected = viewModel::setSortMode,
|
||||
onClearAll = {
|
||||
viewModel.clearAllFilters()
|
||||
isBottomSheetVisible = false
|
||||
},
|
||||
onDismiss = { isBottomSheetVisible = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
|
|
@ -257,54 +272,40 @@ private fun ResourceCard(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onClick
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
// Title
|
||||
Text(
|
||||
text = resource.title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.weight(1f)
|
||||
maxLines = 1
|
||||
)
|
||||
IconButton(
|
||||
onClick = onDownloadClick,
|
||||
enabled = downloadState !is DownloadState.Loading
|
||||
) {
|
||||
if (downloadState is DownloadState.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Download,
|
||||
contentDescription = "Herunterladen"
|
||||
)
|
||||
}
|
||||
// Author
|
||||
resource.author?.let { author ->
|
||||
Text(
|
||||
text = author,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
FormatBadge(format = resource.fileFormat)
|
||||
Text(
|
||||
text = formatFileSize(resource.fileSize),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
if (resource.tags.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
// Overflow row: format badge + size + tags
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
FormatBadge(format = resource.fileFormat)
|
||||
Text(
|
||||
text = formatFileSize(resource.fileSize),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.align(Alignment.CenterVertically)
|
||||
)
|
||||
resource.tags.forEach { tag ->
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
|
|
@ -312,13 +313,169 @@ private fun ResourceCard(
|
|||
) {
|
||||
Text(
|
||||
text = tag,
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 1.dp),
|
||||
style = MaterialTheme.typography.labelSmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Download button
|
||||
IconButton(
|
||||
onClick = onDownloadClick,
|
||||
enabled = downloadState !is DownloadState.Loading
|
||||
) {
|
||||
if (downloadState is DownloadState.Loading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Download,
|
||||
contentDescription = "Herunterladen"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun FilterSortBottomSheet(
|
||||
uiState: ResourceListUiState,
|
||||
onTagToggled: (String) -> Unit,
|
||||
onFormatSelected: (String?) -> Unit,
|
||||
onLanguageSelected: (String?) -> Unit,
|
||||
onSortSelected: (SortMode) -> Unit,
|
||||
onClearAll: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = sheetState
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 32.dp)
|
||||
) {
|
||||
// Tags
|
||||
if (uiState.allTags.isNotEmpty()) {
|
||||
Text(
|
||||
text = "Tags",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
uiState.allTags.forEach { tag ->
|
||||
FilterChip(
|
||||
selected = tag in uiState.selectedTags,
|
||||
onClick = { onTagToggled(tag) },
|
||||
label = { Text(tag) }
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
// Format filter
|
||||
if (uiState.allFormats.size > 1) {
|
||||
Text(
|
||||
text = "Format",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
uiState.allFormats.forEach { format ->
|
||||
FilterChip(
|
||||
selected = uiState.selectedFormat == format,
|
||||
onClick = {
|
||||
onFormatSelected(if (uiState.selectedFormat == format) null else format)
|
||||
},
|
||||
label = { Text(format.uppercase()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
// Language filter
|
||||
if (uiState.allLanguages.size > 1) {
|
||||
Text(
|
||||
text = "Sprache",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
uiState.allLanguages.forEach { lang ->
|
||||
FilterChip(
|
||||
selected = uiState.selectedLanguage == lang,
|
||||
onClick = {
|
||||
onLanguageSelected(if (uiState.selectedLanguage == lang) null else lang)
|
||||
},
|
||||
label = { Text(languageLabel(lang)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
|
||||
// Sort
|
||||
Text(
|
||||
text = "Sortierung",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
SortMode.entries.forEach { mode ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = uiState.sortMode == mode,
|
||||
onClick = { onSortSelected(mode) },
|
||||
role = Role.RadioButton
|
||||
)
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(
|
||||
selected = uiState.sortMode == mode,
|
||||
onClick = null
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = mode.label,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (uiState.activeFilterSortCount > 0) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
TextButton(
|
||||
onClick = onClearAll,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Alle zurücksetzen")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -338,7 +495,7 @@ private fun FormatBadge(format: String) {
|
|||
) {
|
||||
Text(
|
||||
text = format.uppercase(),
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 1.dp),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = color
|
||||
|
|
@ -346,6 +503,14 @@ private fun FormatBadge(format: String) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun languageLabel(code: String): String = when (code) {
|
||||
"de" -> "Deutsch"
|
||||
"en" -> "English"
|
||||
"fr" -> "Français"
|
||||
"es" -> "Español"
|
||||
else -> code.uppercase()
|
||||
}
|
||||
|
||||
private fun formatFileSize(bytes: Long): String {
|
||||
return if (bytes >= 1024 * 1024) {
|
||||
String.format("%.1f MB", bytes / (1024.0 * 1024.0))
|
||||
|
|
|
|||
|
|
@ -33,12 +33,22 @@ internal data class ResourceListUiState(
|
|||
val resources: List<ResourceDto> = emptyList(),
|
||||
val searchQuery: String = "",
|
||||
val selectedTags: Set<String> = emptySet(),
|
||||
val selectedFormat: String? = null,
|
||||
val selectedLanguage: String? = null,
|
||||
val sortMode: SortMode = SortMode.TITLE_ASC,
|
||||
val isLoading: Boolean = false,
|
||||
val allTags: List<String> = emptyList(),
|
||||
val allFormats: List<String> = emptyList(),
|
||||
val allLanguages: List<String> = emptyList(),
|
||||
val downloadStates: Map<String, DownloadState> = emptyMap(),
|
||||
val errorMessage: String? = null
|
||||
)
|
||||
) {
|
||||
val activeFilterSortCount: Int
|
||||
get() = selectedTags.size +
|
||||
(if (selectedFormat != null) 1 else 0) +
|
||||
(if (selectedLanguage != null) 1 else 0) +
|
||||
(if (sortMode != SortMode.TITLE_ASC) 1 else 0)
|
||||
}
|
||||
|
||||
@HiltViewModel
|
||||
internal class ResourceListViewModel @Inject constructor(
|
||||
|
|
@ -52,7 +62,16 @@ internal class ResourceListViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
resourceRepository.getAll().collect { resources ->
|
||||
val allTags = resources.flatMap { it.tags }.distinct().sorted()
|
||||
_uiState.update { it.copy(resources = resources, allTags = allTags) }
|
||||
val allFormats = resources.map { it.fileFormat }.distinct().sorted()
|
||||
val allLanguages = resources.mapNotNull { it.language }.distinct().sorted()
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
resources = resources,
|
||||
allTags = allTags,
|
||||
allFormats = allFormats,
|
||||
allLanguages = allLanguages
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
|
|
@ -90,6 +109,25 @@ internal class ResourceListViewModel @Inject constructor(
|
|||
_uiState.update { it.copy(sortMode = mode) }
|
||||
}
|
||||
|
||||
fun setFormat(format: String?) {
|
||||
_uiState.update { it.copy(selectedFormat = format) }
|
||||
}
|
||||
|
||||
fun setLanguage(language: String?) {
|
||||
_uiState.update { it.copy(selectedLanguage = language) }
|
||||
}
|
||||
|
||||
fun clearAllFilters() {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
selectedTags = emptySet(),
|
||||
selectedFormat = null,
|
||||
selectedLanguage = null,
|
||||
sortMode = SortMode.TITLE_ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadAndOpen(guid: String, mimeType: String, fileFormat: String, context: Context) {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { state ->
|
||||
|
|
|
|||
Loading…
Reference in a new issue