diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt index 8a198b9fd..b9cd3967f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt @@ -63,6 +63,12 @@ fun DataService.libraryLiveData( } } } + + if (labels.isNotEmpty()) { + mediatorLiveData.value = (mediatorLiveData.value ?: listOf()).filter { + it.labels.intersect(labels.toSet()).any() + } + } } return mediatorLiveData diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt index 0829aa8f3..1e3dad14a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryFilterBar.kt @@ -1,64 +1,102 @@ package app.omnivore.omnivore.ui.library import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import app.omnivore.omnivore.persistence.entities.SavedItemLabel +import app.omnivore.omnivore.ui.components.LabelChipColors @OptIn(ExperimentalMaterial3Api::class) @Composable fun LibraryFilterBar(viewModel: LibraryViewModel) { var isSavedItemFilterMenuExpanded by remember { mutableStateOf(false) } val activeSavedItemFilter: SavedItemFilter by viewModel.appliedFilterLiveData.observeAsState(SavedItemFilter.INBOX) + val activeLabels: List by viewModel.activeLabelsLiveData.observeAsState(listOf()) var isSavedItemSortFilterMenuExpanded by remember { mutableStateOf(false) } val activeSavedItemSortFilter: SavedItemSortFilter by viewModel.appliedSortFilterLiveData.observeAsState(SavedItemSortFilter.NEWEST) + val listState = rememberLazyListState() Column { - Row( + LazyRow( + state = listState, horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically, modifier = Modifier + .padding(start = 6.dp) .fillMaxWidth() ) { - AssistChip( - onClick = { isSavedItemFilterMenuExpanded = true }, - label = { Text(activeSavedItemFilter.displayText) }, - trailingIcon = { - Icon( - Icons.Default.ArrowDropDown, - contentDescription = "drop down button to change primary library filter" - ) - }, - modifier = Modifier.padding(end = 6.dp) - ) - AssistChip( - onClick = { isSavedItemSortFilterMenuExpanded = true }, - label = { Text(activeSavedItemSortFilter.displayText) }, - trailingIcon = { - Icon( - Icons.Default.ArrowDropDown, - contentDescription = "drop down button to change library sort order" - ) - }, - modifier = Modifier.padding(end = 6.dp) - ) - AssistChip( - onClick = { viewModel.showLabelsSelectionSheetLiveData.value = true }, - label = { Text("Labels") }, - trailingIcon = { - Icon( - Icons.Default.ArrowDropDown, - contentDescription = "drop down button to open label selection sheet" - ) - }, - modifier = Modifier.padding(end = 6.dp) - ) + item { + AssistChip( + onClick = { isSavedItemFilterMenuExpanded = true }, + label = { Text(activeSavedItemFilter.displayText) }, + trailingIcon = { + Icon( + Icons.Default.ArrowDropDown, + contentDescription = "drop down button to change primary library filter" + ) + }, + modifier = Modifier.padding(end = 6.dp) + ) + AssistChip( + onClick = { isSavedItemSortFilterMenuExpanded = true }, + label = { Text(activeSavedItemSortFilter.displayText) }, + trailingIcon = { + Icon( + Icons.Default.ArrowDropDown, + contentDescription = "drop down button to change library sort order" + ) + }, + modifier = Modifier.padding(end = 6.dp) + ) + AssistChip( + onClick = { viewModel.showLabelsSelectionSheetLiveData.value = true }, + label = { Text("Labels") }, + trailingIcon = { + Icon( + Icons.Default.ArrowDropDown, + contentDescription = "drop down button to open label selection sheet" + ) + }, + modifier = Modifier.padding(end = 6.dp) + ) + } + items(activeLabels.sortedBy { it.name }) { label -> + val chipColors = LabelChipColors.fromHex(label.color) + + AssistChip( + onClick = { + viewModel.updateAppliedLabels( + (viewModel.activeLabelsLiveData.value ?: listOf()).filter { it.savedItemLabelId != label.savedItemLabelId } + ) + }, + label = { Text(label.name) }, + border = null, + colors = SuggestionChipDefaults.elevatedSuggestionChipColors( + containerColor = chipColors.containerColor, + labelColor = chipColors.textColor, + iconContentColor = chipColors.textColor + ), + trailingIcon = { + Icon( + Icons.Default.Close, + contentDescription = "close icon to remove label" + ) + }, + modifier = Modifier + .padding(horizontal = 4.dp) + ) + } } SavedItemFilterContextMenu( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index 823d2b5ff..a43fd26f4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -147,95 +147,102 @@ fun InfiniteListHandler( } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun LabelsSelectionSheet(viewModel: LibraryViewModel) { - val listState = rememberLazyListState() val isActive: Boolean by viewModel.showLabelsSelectionSheetLiveData.observeAsState(false) - val labels: List by viewModel.savedItemLabelsLiveData.observeAsState(listOf()) - val selectedLabels = remember { mutableStateOf(viewModel.activeLabelsLiveData.value) } if (isActive) { Dialog(onDismissRequest = { viewModel.showLabelsSelectionSheetLiveData.value = false } ) { - Surface( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background), - shape = RoundedCornerShape(16.dp) - ) { - LazyColumn( - state = listState, - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.CenterHorizontally, + LabelsSelectionSheetContent(viewModel = viewModel, initialSelectedLabels = viewModel.activeLabelsLiveData.value ?: listOf()) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LabelsSelectionSheetContent(viewModel: LibraryViewModel, initialSelectedLabels: List) { + val listState = rememberLazyListState() + val labels: List by viewModel.savedItemLabelsLiveData.observeAsState(listOf()) + val selectedLabels = remember { mutableStateOf(initialSelectedLabels) } + + Surface( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + shape = RoundedCornerShape(16.dp) + ) { + LazyColumn( + state = listState, + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 6.dp) + ) { + item { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .fillMaxSize() - .padding(horizontal = 6.dp) + .fillMaxWidth() ) { - item { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - ) { - TextButton(onClick = { viewModel.showLabelsSelectionSheetLiveData.value = false }) { - Text(text = "Cancel") - } - - Text("Filter by Label", fontWeight = FontWeight.ExtraBold) - - TextButton( - onClick = { - selectedLabels.value?.let { - viewModel.activeLabelsLiveData.value = it - } - viewModel.showLabelsSelectionSheetLiveData.value = false - } - ) { - Text(text = "Done") - } - } + TextButton(onClick = { viewModel.showLabelsSelectionSheetLiveData.value = false }) { + Text(text = "Cancel") } - items(labels) { label -> - val isLabelSelected = (selectedLabels.value ?: listOf()).contains(label) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable { - if (isLabelSelected) { - selectedLabels.value = (selectedLabels.value ?: listOf()).filter { it.savedItemLabelId != label.savedItemLabelId } - } else { - selectedLabels.value = (selectedLabels.value ?: listOf()) + listOf(label) - } - } - .padding(horizontal = 6.dp) - ) { - val chipColors = LabelChipColors.fromHex(label.color) + Text("Filter by Label", fontWeight = FontWeight.ExtraBold) - SuggestionChip( - onClick = {}, - label = { Text(label.name) }, - border = null, - colors = SuggestionChipDefaults.elevatedSuggestionChipColors( - containerColor = chipColors.containerColor, - labelColor = chipColors.textColor, - iconContentColor = chipColors.textColor - ) - ) - if (isLabelSelected) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = null - ) + TextButton( + onClick = { + selectedLabels.value?.let { + viewModel.updateAppliedLabels(it) } + viewModel.showLabelsSelectionSheetLiveData.value = false } - Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp) + ) { + Text(text = "Done") } } } + items(labels) { label -> + val isLabelSelected = (selectedLabels.value ?: listOf()).contains(label) + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .clickable { + if (isLabelSelected) { + selectedLabels.value = (selectedLabels.value + ?: listOf()).filter { it.savedItemLabelId != label.savedItemLabelId } + } else { + selectedLabels.value = (selectedLabels.value ?: listOf()) + listOf(label) + } + } + .padding(horizontal = 6.dp) + ) { + val chipColors = LabelChipColors.fromHex(label.color) + + SuggestionChip( + onClick = {}, + label = { Text(label.name) }, + border = null, + colors = SuggestionChipDefaults.elevatedSuggestionChipColors( + containerColor = chipColors.containerColor, + labelColor = chipColors.textColor, + iconContentColor = chipColors.textColor + ) + ) + if (isLabelSelected) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = null + ) + } + } + Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp) + } } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index 8591eb275..cc8730d76 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -40,13 +40,12 @@ class LibraryViewModel @Inject constructor( val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST) val showLabelsSelectionSheetLiveData = MutableLiveData(false) val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData() + val activeLabelsLiveData = MutableLiveData>(listOf()) var isRefreshing by mutableStateOf(false) var showSearchField by mutableStateOf(false) var hasLoadedInitialFilters = false - var activeLabelsLiveData = MutableLiveData>(listOf()) - fun loadInitialFilterValues() { if (hasLoadedInitialFilters) { return } hasLoadedInitialFilters = false @@ -129,11 +128,18 @@ class LibraryViewModel @Inject constructor( } } + fun updateAppliedLabels(labels: List) { + viewModelScope.launch { + activeLabelsLiveData.value = labels + handleFilterChanges() + } + } + suspend fun handleFilterChanges() { if (searchTextLiveData.value != "") { performSearch(true) } else if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) { - itemsLiveDataInternal = dataService.libraryLiveData(appliedFilterLiveData.value!!, appliedSortFilterLiveData.value!!, listOf()) + itemsLiveDataInternal = dataService.libraryLiveData(appliedFilterLiveData.value!!, appliedSortFilterLiveData.value!!, activeLabelsLiveData.value ?: listOf()) itemsLiveData.removeSource(itemsLiveDataInternal) itemsLiveData.addSource(itemsLiveDataInternal, itemsLiveData::setValue) }