apply label filters in library live data
This commit is contained in:
@ -63,6 +63,12 @@ fun DataService.libraryLiveData(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.isNotEmpty()) {
|
||||
mediatorLiveData.value = (mediatorLiveData.value ?: listOf()).filter {
|
||||
it.labels.intersect(labels.toSet()).any()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mediatorLiveData
|
||||
|
||||
@ -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<SavedItemLabel> 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(
|
||||
|
||||
@ -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<SavedItemLabel> 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<SavedItemLabel>) {
|
||||
val listState = rememberLazyListState()
|
||||
val labels: List<SavedItemLabel> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<List<SavedItemLabel>>(listOf())
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
var showSearchField by mutableStateOf(false)
|
||||
var hasLoadedInitialFilters = false
|
||||
|
||||
var activeLabelsLiveData = MutableLiveData<List<SavedItemLabel>>(listOf())
|
||||
|
||||
fun loadInitialFilterValues() {
|
||||
if (hasLoadedInitialFilters) { return }
|
||||
hasLoadedInitialFilters = false
|
||||
@ -129,11 +128,18 @@ class LibraryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAppliedLabels(labels: List<SavedItemLabel>) {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user