fix following filters
This commit is contained in:
@ -26,7 +26,7 @@ interface SavedItemDao {
|
||||
suspend fun getSavedItemWithLabelsAndHighlights(slug: String): SavedItemWithLabelsAndHighlights?
|
||||
|
||||
@Query("DELETE FROM savedItem WHERE savedItemId = :itemID")
|
||||
fun deleteById(itemID: String)
|
||||
suspend fun deleteById(itemID: String)
|
||||
|
||||
@Query("DELETE FROM savedItem WHERE savedItemId in (:itemIDs)")
|
||||
fun deleteByIds(itemIDs: List<String>)
|
||||
@ -80,7 +80,12 @@ interface SavedItemDao {
|
||||
"AND SavedItem.isArchived IN (:allowedArchiveStates) " +
|
||||
"AND SavedItem.contentReader IN (:allowedContentReaders) " +
|
||||
"AND CASE WHEN :hasRequiredLabels THEN SavedItemLabel.name in (:requiredLabels) ELSE 1 END " +
|
||||
"AND CASE WHEN :hasExcludedLabels THEN SavedItemLabel.name is NULL OR SavedItemLabel.name not in (:excludedLabels) ELSE 1 END " +
|
||||
"AND CASE WHEN :hasExcludedLabels THEN NOT EXISTS ( " +
|
||||
" SELECT 1 FROM SavedItemAndSavedItemLabelCrossRef " +
|
||||
" INNER JOIN SavedItemLabel ON SavedItemAndSavedItemLabelCrossRef.savedItemLabelId = SavedItemLabel.savedItemLabelId " +
|
||||
" WHERE SavedItemAndSavedItemLabelCrossRef.savedItemId = SavedItem.savedItemId " +
|
||||
" AND SavedItemLabel.name IN (:excludedLabels) " +
|
||||
") ELSE 1 END " +
|
||||
|
||||
"GROUP BY SavedItem.savedItemId " +
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ import app.omnivore.omnivore.feature.library.LabelBottomSheet
|
||||
import app.omnivore.omnivore.feature.library.LibraryBottomSheetState
|
||||
import app.omnivore.omnivore.feature.library.LibraryNavigationBar
|
||||
import app.omnivore.omnivore.feature.library.LibraryViewContent
|
||||
import app.omnivore.omnivore.feature.library.SavedItemSortFilter
|
||||
import app.omnivore.omnivore.feature.save.SaveViewModel
|
||||
import app.omnivore.omnivore.navigation.Routes
|
||||
import app.omnivore.omnivore.navigation.TopLevelDestination
|
||||
@ -46,10 +47,6 @@ internal fun FollowingScreen(
|
||||
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
val showBottomSheet: LibraryBottomSheetState by viewModel.bottomSheetState.observeAsState(
|
||||
LibraryBottomSheetState.HIDDEN
|
||||
)
|
||||
|
||||
viewModel.snackbarMessage?.let {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
@ -58,9 +55,15 @@ internal fun FollowingScreen(
|
||||
}
|
||||
|
||||
val labels by viewModel.labelsState.collectAsStateWithLifecycle()
|
||||
val currentTopLevelDestination =
|
||||
TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState()
|
||||
val savedItemFilter by viewModel.appliedFilterState.collectAsStateWithLifecycle()
|
||||
val activeLabels by viewModel.activeLabels.collectAsStateWithLifecycle()
|
||||
val sortFilter: SavedItemSortFilter by viewModel.appliedSortFilterLiveData.collectAsStateWithLifecycle()
|
||||
val bottomSheetState: LibraryBottomSheetState by viewModel.bottomSheetState.collectAsStateWithLifecycle()
|
||||
|
||||
when (showBottomSheet) {
|
||||
when (bottomSheetState) {
|
||||
LibraryBottomSheetState.ADD_LINK -> {
|
||||
AddLinkBottomSheet(saveViewModel) {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN
|
||||
@ -100,9 +103,6 @@ internal fun FollowingScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val currentTopLevelDestination = TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
LibraryNavigationBar(
|
||||
@ -118,6 +118,13 @@ internal fun FollowingScreen(
|
||||
when (uiState) {
|
||||
is FollowingUiState.Success -> {
|
||||
LibraryViewContent(
|
||||
itemsFilter = savedItemFilter,
|
||||
activeLabels = activeLabels,
|
||||
sortFilter = sortFilter,
|
||||
updateSavedItemFilter = { viewModel.updateSavedItemFilter(it) },
|
||||
updateSavedItemSortFilter = { viewModel.updateSavedItemSortFilter(it) },
|
||||
setBottomSheetState = { viewModel.setBottomSheetState(it) },
|
||||
updateAppliedLabels = { viewModel.updateAppliedLabels(it) },
|
||||
isFollowingScreen = currentTopLevelDestination == TopLevelDestination.FOLLOWING,
|
||||
{ viewModel.actionsMenuItemLiveData.postValue(null) },
|
||||
savedItemViewModel = viewModel,
|
||||
|
||||
@ -55,7 +55,7 @@ class FollowingViewModel @Inject constructor(
|
||||
LibraryQuery(
|
||||
allowedArchiveStates = listOf(0),
|
||||
sortKey = "newest",
|
||||
requiredLabels = listOf(),
|
||||
requiredLabels = listOf("Newsletter", "RSS"),
|
||||
excludedLabels = listOf(),
|
||||
allowedContentReaders = listOf("WEB", "PDF", "EPUB")
|
||||
)
|
||||
@ -68,12 +68,11 @@ class FollowingViewModel @Inject constructor(
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = FollowingUiState.Loading
|
||||
)
|
||||
|
||||
val appliedFilterState = MutableStateFlow(SavedItemFilter.FOLLOWING)
|
||||
val appliedSortFilterLiveData = MutableStateFlow(SavedItemSortFilter.NEWEST)
|
||||
val bottomSheetState = MutableStateFlow(LibraryBottomSheetState.HIDDEN)
|
||||
|
||||
val appliedFilterLiveData = MutableLiveData(
|
||||
SavedItemFilter.FOLLOWING
|
||||
)
|
||||
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
|
||||
val bottomSheetState = MutableLiveData(LibraryBottomSheetState.HIDDEN)
|
||||
val currentItem = mutableStateOf<String?>(null)
|
||||
|
||||
val labelsState = libraryRepository.getSavedItemsLabels().stateIn(
|
||||
@ -95,7 +94,7 @@ class FollowingViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
updateSavedItemFilter(appliedFilterLiveData.value ?: SavedItemFilter.INBOX)
|
||||
updateSavedItemFilter(appliedFilterState.value)
|
||||
}
|
||||
|
||||
private fun syncLabels() {
|
||||
@ -114,6 +113,10 @@ class FollowingViewModel @Inject constructor(
|
||||
load()
|
||||
}
|
||||
|
||||
fun setBottomSheetState(state: LibraryBottomSheetState) {
|
||||
bottomSheetState.value = state
|
||||
}
|
||||
|
||||
private fun getLastSyncTime(): Instant? = runBlocking {
|
||||
datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let {
|
||||
try {
|
||||
@ -159,10 +162,10 @@ class FollowingViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSavedItemFilter(filter: SavedItemFilter) {
|
||||
fun updateSavedItemFilter(filter: SavedItemFilter) {
|
||||
viewModelScope.launch {
|
||||
datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue)
|
||||
appliedFilterLiveData.value = filter
|
||||
appliedFilterState.value = filter
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
@ -185,50 +188,47 @@ class FollowingViewModel @Inject constructor(
|
||||
private fun handleFilterChanges() {
|
||||
librarySearchCursor = null
|
||||
|
||||
if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) {
|
||||
val sortKey = when (appliedSortFilterLiveData.value) {
|
||||
SavedItemSortFilter.NEWEST -> "newest"
|
||||
SavedItemSortFilter.OLDEST -> "oldest"
|
||||
SavedItemSortFilter.RECENTLY_READ -> "recentlyRead"
|
||||
SavedItemSortFilter.RECENTLY_PUBLISHED -> "recentlyPublished"
|
||||
else -> "newest"
|
||||
}
|
||||
|
||||
val allowedArchiveStates = when (appliedFilterLiveData.value) {
|
||||
SavedItemFilter.ALL -> listOf(0, 1)
|
||||
SavedItemFilter.ARCHIVED -> listOf(1)
|
||||
else -> listOf(0)
|
||||
}
|
||||
|
||||
val allowedContentReaders = when (appliedFilterLiveData.value) {
|
||||
SavedItemFilter.FILES -> listOf("PDF", "EPUB")
|
||||
else -> listOf("WEB", "PDF", "EPUB")
|
||||
}
|
||||
|
||||
var requiredLabels = when (appliedFilterLiveData.value) {
|
||||
SavedItemFilter.NEWSLETTERS -> listOf("Newsletter")
|
||||
SavedItemFilter.FEEDS -> listOf("RSS")
|
||||
else -> activeLabels.value.map { it.name }
|
||||
}
|
||||
|
||||
activeLabels.value.let { it ->
|
||||
requiredLabels = requiredLabels + it.map { it.name }
|
||||
}
|
||||
|
||||
|
||||
val excludeLabels = when (appliedFilterLiveData.value) {
|
||||
SavedItemFilter.READ_LATER -> listOf("Newsletter", "RSS")
|
||||
else -> listOf()
|
||||
}
|
||||
|
||||
_libraryQuery.value = LibraryQuery(
|
||||
allowedArchiveStates = allowedArchiveStates,
|
||||
sortKey = sortKey,
|
||||
requiredLabels = requiredLabels,
|
||||
excludedLabels = excludeLabels,
|
||||
allowedContentReaders = allowedContentReaders
|
||||
)
|
||||
val sortKey = when (appliedSortFilterLiveData.value) {
|
||||
SavedItemSortFilter.NEWEST -> "newest"
|
||||
SavedItemSortFilter.OLDEST -> "oldest"
|
||||
SavedItemSortFilter.RECENTLY_READ -> "recentlyRead"
|
||||
SavedItemSortFilter.RECENTLY_PUBLISHED -> "recentlyPublished"
|
||||
}
|
||||
|
||||
val allowedArchiveStates = when (appliedFilterState.value) {
|
||||
SavedItemFilter.ALL -> listOf(0, 1)
|
||||
SavedItemFilter.ARCHIVED -> listOf(1)
|
||||
else -> listOf(0)
|
||||
}
|
||||
|
||||
val allowedContentReaders = when (appliedFilterState.value) {
|
||||
SavedItemFilter.FILES -> listOf("PDF", "EPUB")
|
||||
else -> listOf("WEB", "PDF", "EPUB")
|
||||
}
|
||||
|
||||
var requiredLabels = when (appliedFilterState.value) {
|
||||
SavedItemFilter.NEWSLETTERS -> listOf("Newsletter")
|
||||
SavedItemFilter.FEEDS -> listOf("RSS")
|
||||
else -> activeLabels.value.map { it.name }
|
||||
}
|
||||
|
||||
activeLabels.value.let { it ->
|
||||
requiredLabels = requiredLabels + it.map { it.name }
|
||||
}
|
||||
|
||||
|
||||
val excludeLabels = when (appliedFilterState.value) {
|
||||
SavedItemFilter.NON_FEED -> listOf("Newsletter", "RSS")
|
||||
else -> listOf()
|
||||
}
|
||||
|
||||
_libraryQuery.value = LibraryQuery(
|
||||
allowedArchiveStates = allowedArchiveStates,
|
||||
sortKey = sortKey,
|
||||
requiredLabels = requiredLabels + listOf("Newsletter", "RSS"),
|
||||
excludedLabels = excludeLabels,
|
||||
allowedContentReaders = allowedContentReaders
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun syncItems() {
|
||||
@ -365,7 +365,7 @@ class FollowingViewModel @Inject constructor(
|
||||
|
||||
private fun searchQueryString(): String {
|
||||
var query =
|
||||
"${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
"${appliedFilterState.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
|
||||
activeLabels.value.let {
|
||||
if (it.isNotEmpty()) {
|
||||
|
||||
@ -16,7 +16,6 @@ import androidx.compose.material3.SuggestionChipDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -26,8 +25,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.toLowerCase
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
|
||||
import app.omnivore.omnivore.feature.components.LabelChipColors
|
||||
@ -35,19 +32,19 @@ import app.omnivore.omnivore.feature.components.LabelChipColors
|
||||
@Composable
|
||||
fun LibraryFilterBar(
|
||||
isFollowingScreen: Boolean,
|
||||
viewModel: LibraryViewModel = hiltViewModel()
|
||||
itemsFilter: SavedItemFilter,
|
||||
sortFilter: SavedItemSortFilter,
|
||||
activeLabels: List<SavedItemLabel>,
|
||||
setBottomSheetState: (LibraryBottomSheetState) -> Unit,
|
||||
updateSavedItemFilter: (SavedItemFilter) -> Unit,
|
||||
updateSavedItemSortFilter: (SavedItemSortFilter) -> Unit,
|
||||
updateAppliedLabels: (List<SavedItemLabel>) -> Unit
|
||||
) {
|
||||
|
||||
var isSavedItemFilterMenuExpanded by remember { mutableStateOf(false) }
|
||||
val activeSavedItemFilter: SavedItemFilter by viewModel.appliedFilterLiveData.observeAsState(
|
||||
if (isFollowingScreen) SavedItemFilter.FOLLOWING else SavedItemFilter.INBOX
|
||||
)
|
||||
val activeLabels: List<SavedItemLabel> by viewModel.activeLabels.collectAsStateWithLifecycle()
|
||||
|
||||
|
||||
var isSavedItemSortFilterMenuExpanded by remember { mutableStateOf(false) }
|
||||
val activeSavedItemSortFilter: SavedItemSortFilter by viewModel.appliedSortFilterLiveData.observeAsState(
|
||||
SavedItemSortFilter.NEWEST
|
||||
)
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
Column {
|
||||
@ -62,7 +59,7 @@ fun LibraryFilterBar(
|
||||
item {
|
||||
AssistChip(onClick = { isSavedItemFilterMenuExpanded = true },
|
||||
label = { Text(
|
||||
activeSavedItemFilter.displayText
|
||||
itemsFilter.displayText
|
||||
) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
@ -73,7 +70,7 @@ fun LibraryFilterBar(
|
||||
modifier = Modifier.padding(end = 6.dp)
|
||||
)
|
||||
AssistChip(onClick = { isSavedItemSortFilterMenuExpanded = true },
|
||||
label = { Text(activeSavedItemSortFilter.displayText) },
|
||||
label = { Text(sortFilter.displayText) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
Icons.Default.ArrowDropDown,
|
||||
@ -83,7 +80,7 @@ fun LibraryFilterBar(
|
||||
modifier = Modifier.padding(end = 6.dp)
|
||||
)
|
||||
AssistChip(
|
||||
onClick = { viewModel.bottomSheetState.value = LibraryBottomSheetState.LABEL },
|
||||
onClick = { setBottomSheetState(LibraryBottomSheetState.LABEL) },
|
||||
label = { Text(stringResource(R.string.library_filter_bar_label_labels)) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
@ -97,10 +94,12 @@ fun LibraryFilterBar(
|
||||
items(activeLabels.sortedWith(compareBy { it.name.toLowerCase(Locale.current) })) { label ->
|
||||
val chipColors = LabelChipColors.fromHex(label.color)
|
||||
|
||||
AssistChip(onClick = {
|
||||
viewModel.updateAppliedLabels((viewModel.activeLabels.value
|
||||
?: listOf()).filter { it.savedItemLabelId != label.savedItemLabelId })
|
||||
},
|
||||
AssistChip(
|
||||
onClick = {
|
||||
updateAppliedLabels(
|
||||
activeLabels.filter { it.savedItemLabelId != label.savedItemLabelId }
|
||||
)
|
||||
},
|
||||
label = { Text(label.name) },
|
||||
border = null,
|
||||
colors = SuggestionChipDefaults.elevatedSuggestionChipColors(
|
||||
@ -122,11 +121,11 @@ fun LibraryFilterBar(
|
||||
isFollowingScreen = isFollowingScreen,
|
||||
isExpanded = isSavedItemFilterMenuExpanded,
|
||||
onDismiss = { isSavedItemFilterMenuExpanded = false },
|
||||
actionHandler = { viewModel.updateSavedItemFilter(it) }
|
||||
actionHandler = { updateSavedItemFilter(it) }
|
||||
)
|
||||
|
||||
SavedItemSortFilterContextMenu(isExpanded = isSavedItemSortFilterMenuExpanded,
|
||||
onDismiss = { isSavedItemSortFilterMenuExpanded = false },
|
||||
actionHandler = { viewModel.updateSavedItemSortFilter(it) })
|
||||
actionHandler = { updateSavedItemSortFilter(it) })
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,10 +90,6 @@ internal fun LibraryView(
|
||||
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
val showBottomSheet: LibraryBottomSheetState by viewModel.bottomSheetState.observeAsState(
|
||||
LibraryBottomSheetState.HIDDEN
|
||||
)
|
||||
|
||||
viewModel.snackbarMessage?.let {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
@ -102,9 +98,15 @@ internal fun LibraryView(
|
||||
}
|
||||
|
||||
val labels by viewModel.labelsState.collectAsStateWithLifecycle()
|
||||
val currentTopLevelDestination =
|
||||
TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState()
|
||||
val savedItemFilter by viewModel.appliedFilterState.collectAsStateWithLifecycle()
|
||||
val activeLabels by viewModel.activeLabels.collectAsStateWithLifecycle()
|
||||
val sortFilter: SavedItemSortFilter by viewModel.appliedSortFilterLiveData.collectAsStateWithLifecycle()
|
||||
val bottomSheetState: LibraryBottomSheetState by viewModel.bottomSheetState.collectAsStateWithLifecycle()
|
||||
|
||||
when (showBottomSheet) {
|
||||
when (bottomSheetState) {
|
||||
LibraryBottomSheetState.ADD_LINK -> {
|
||||
AddLinkBottomSheet(saveViewModel) {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN
|
||||
@ -112,8 +114,7 @@ internal fun LibraryView(
|
||||
}
|
||||
|
||||
LibraryBottomSheetState.LABEL -> {
|
||||
LabelBottomSheet(
|
||||
deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
LabelBottomSheet(deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
labels = labels,
|
||||
currentSavedItemData = viewModel.currentSavedItemUnderEdit(),
|
||||
labelsViewModel,
|
||||
@ -125,13 +126,11 @@ internal fun LibraryView(
|
||||
viewModel.updateSavedItemLabels(savedItemId, labels)
|
||||
},
|
||||
activeLabels,
|
||||
{ viewModel.updateAppliedLabels(it) }
|
||||
)
|
||||
{ viewModel.updateAppliedLabels(it) })
|
||||
}
|
||||
|
||||
LibraryBottomSheetState.EDIT -> {
|
||||
EditBottomSheet(
|
||||
editInfoViewModel,
|
||||
EditBottomSheet(editInfoViewModel,
|
||||
deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
{ viewModel.refresh() },
|
||||
viewModel.currentSavedItemUnderEdit()
|
||||
@ -144,24 +143,26 @@ internal fun LibraryView(
|
||||
}
|
||||
}
|
||||
|
||||
val currentTopLevelDestination = TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
LibraryNavigationBar(
|
||||
currentDestination = currentTopLevelDestination,
|
||||
LibraryNavigationBar(currentDestination = currentTopLevelDestination,
|
||||
savedItemViewModel = viewModel,
|
||||
onSearchClicked = { navController.navigate(Routes.Search.route) },
|
||||
onAddLinkClicked = {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
) { paddingValues ->
|
||||
when (uiState) {
|
||||
is LibraryUiState.Success -> {
|
||||
LibraryViewContent(
|
||||
itemsFilter = savedItemFilter,
|
||||
activeLabels = activeLabels,
|
||||
sortFilter = sortFilter,
|
||||
updateSavedItemFilter = { viewModel.updateSavedItemFilter(it) },
|
||||
updateSavedItemSortFilter = { viewModel.updateSavedItemSortFilter(it) },
|
||||
setBottomSheetState = { viewModel.setBottomSheetState(it) },
|
||||
updateAppliedLabels = { viewModel.updateAppliedLabels(it) },
|
||||
isFollowingScreen = currentTopLevelDestination == TopLevelDestination.FOLLOWING,
|
||||
{ viewModel.actionsMenuItemLiveData.postValue(null) },
|
||||
savedItemViewModel = viewModel,
|
||||
@ -176,9 +177,9 @@ internal fun LibraryView(
|
||||
viewModel.handleSavedItemAction(id, action)
|
||||
},
|
||||
{ viewModel.loadUsingSearchAPI() },
|
||||
{ viewModel.initialLoad() }
|
||||
)
|
||||
{ viewModel.initialLoad() })
|
||||
}
|
||||
|
||||
is LibraryUiState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@ -186,9 +187,10 @@ internal fun LibraryView(
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(strokeCap = StrokeCap.Round)
|
||||
CircularProgressIndicator(strokeCap = StrokeCap.Round)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// TODO
|
||||
}
|
||||
@ -218,8 +220,7 @@ fun LabelBottomSheet(
|
||||
) {
|
||||
|
||||
if (currentSavedItemData != null) {
|
||||
LabelsSelectionSheetContent(
|
||||
labels = labels,
|
||||
LabelsSelectionSheetContent(labels = labels,
|
||||
labelsViewModel = labelsViewModel,
|
||||
initialSelectedLabels = currentSavedItemData.labels,
|
||||
onCancel = {
|
||||
@ -236,11 +237,9 @@ fun LabelBottomSheet(
|
||||
},
|
||||
onCreateLabel = { newLabelName, labelHexValue ->
|
||||
createNewSavedItemLabel(newLabelName, labelHexValue)
|
||||
}
|
||||
)
|
||||
})
|
||||
} else { // Is used in library mode
|
||||
LabelsSelectionSheetContent(
|
||||
labels = labels,
|
||||
LabelsSelectionSheetContent(labels = labels,
|
||||
labelsViewModel = labelsViewModel,
|
||||
initialSelectedLabels = activeLabels,
|
||||
onCancel = { onDismiss() },
|
||||
@ -252,8 +251,7 @@ fun LabelBottomSheet(
|
||||
},
|
||||
onCreateLabel = { newLabelName, labelHexValue ->
|
||||
createNewSavedItemLabel(newLabelName, labelHexValue)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,8 +259,7 @@ fun LabelBottomSheet(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddLinkBottomSheet(
|
||||
saveViewModel: SaveViewModel,
|
||||
onDismiss: () -> Unit = {}
|
||||
saveViewModel: SaveViewModel, onDismiss: () -> Unit = {}
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { onDismiss() },
|
||||
@ -272,17 +269,13 @@ fun AddLinkBottomSheet(
|
||||
),
|
||||
) {
|
||||
|
||||
AddLinkSheetContent(
|
||||
viewModel = saveViewModel,
|
||||
onCancel = {
|
||||
saveViewModel.state.value = SaveState.DEFAULT
|
||||
onDismiss()
|
||||
},
|
||||
onLinkAdded = {
|
||||
saveViewModel.state.value = SaveState.DEFAULT
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
AddLinkSheetContent(viewModel = saveViewModel, onCancel = {
|
||||
saveViewModel.state.value = SaveState.DEFAULT
|
||||
onDismiss()
|
||||
}, onLinkAdded = {
|
||||
saveViewModel.state.value = SaveState.DEFAULT
|
||||
onDismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,8 +295,7 @@ fun EditBottomSheet(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
) {
|
||||
EditInfoSheetContent(
|
||||
savedItemId = currentSavedItemUnderEdit?.savedItem?.savedItemId,
|
||||
EditInfoSheetContent(savedItemId = currentSavedItemUnderEdit?.savedItem?.savedItemId,
|
||||
title = currentSavedItemUnderEdit?.savedItem?.title,
|
||||
author = currentSavedItemUnderEdit?.savedItem?.author,
|
||||
description = currentSavedItemUnderEdit?.savedItem?.descriptionText,
|
||||
@ -316,8 +308,7 @@ fun EditBottomSheet(
|
||||
deleteCurrentItem()
|
||||
refresh()
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,6 +316,13 @@ fun EditBottomSheet(
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LibraryViewContent(
|
||||
itemsFilter: SavedItemFilter,
|
||||
activeLabels: List<SavedItemLabel>,
|
||||
sortFilter: SavedItemSortFilter,
|
||||
updateSavedItemFilter:(SavedItemFilter) -> Unit,
|
||||
updateSavedItemSortFilter: (SavedItemSortFilter) -> Unit,
|
||||
setBottomSheetState: (LibraryBottomSheetState) -> Unit,
|
||||
updateAppliedLabels: (List<SavedItemLabel>) -> Unit,
|
||||
isFollowingScreen: Boolean,
|
||||
selectItem: () -> Unit,
|
||||
savedItemViewModel: SavedItemViewModel,
|
||||
@ -359,54 +357,63 @@ fun LibraryViewContent(
|
||||
.nestedScroll(pullToRefreshState.nestedScrollConnection)
|
||||
) {
|
||||
Column {
|
||||
LibraryFilterBar(isFollowingScreen)
|
||||
LibraryFilterBar(
|
||||
isFollowingScreen,
|
||||
itemsFilter,
|
||||
sortFilter,
|
||||
activeLabels,
|
||||
setBottomSheetState,
|
||||
updateSavedItemFilter,
|
||||
updateSavedItemSortFilter,
|
||||
updateAppliedLabels
|
||||
)
|
||||
HorizontalDivider()
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
items(
|
||||
items = items,
|
||||
key = { item -> item.savedItem.savedItemId }
|
||||
) { cardDataWithLabels ->
|
||||
items(items = items,
|
||||
key = { item -> item.savedItem.savedItemId }) { cardDataWithLabels ->
|
||||
val swipeThreshold = 0.45f
|
||||
val currentThresholdFraction = remember { mutableStateOf(0f) }
|
||||
val currentItem by rememberUpdatedState(cardDataWithLabels.savedItem)
|
||||
val swipeState = rememberDismissState(
|
||||
confirmStateChange = {
|
||||
when(it) {
|
||||
DismissValue.Default -> {
|
||||
val swipeState = rememberDismissState(confirmStateChange = {
|
||||
when (it) {
|
||||
DismissValue.Default -> {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
|
||||
DismissValue.DismissedToEnd -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
DismissValue.DismissedToEnd -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
}
|
||||
DismissValue.DismissedToStart -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving.
|
||||
if (currentItem.isArchived) {
|
||||
onUnarchive(currentItem.savedItemId)
|
||||
} else {
|
||||
onArchive(currentItem.savedItemId)
|
||||
DismissValue.DismissedToStart -> {
|
||||
if (currentThresholdFraction.value < swipeThreshold) {
|
||||
return@rememberDismissState false
|
||||
}
|
||||
} else if (it == DismissValue.DismissedToStart) { // Deleting.
|
||||
onDelete(currentItem.savedItemId)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
)
|
||||
|
||||
if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving.
|
||||
if (currentItem.isArchived) {
|
||||
onUnarchive(currentItem.savedItemId)
|
||||
} else {
|
||||
onArchive(currentItem.savedItemId)
|
||||
}
|
||||
} else if (it == DismissValue.DismissedToStart) { // Deleting.
|
||||
onDelete(currentItem.savedItemId)
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
SwipeToDismiss(
|
||||
state = swipeState,
|
||||
directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart),
|
||||
directions = setOf(
|
||||
DismissDirection.StartToEnd, DismissDirection.EndToStart
|
||||
),
|
||||
dismissThresholds = { FractionalThreshold(swipeThreshold) },
|
||||
background = {
|
||||
val direction = swipeState.dismissDirection ?: return@SwipeToDismiss
|
||||
@ -434,8 +441,7 @@ fun LibraryViewContent(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(color)
|
||||
.padding(horizontal = 20.dp),
|
||||
contentAlignment = alignment
|
||||
.padding(horizontal = 20.dp), contentAlignment = alignment
|
||||
) {
|
||||
currentThresholdFraction.value = swipeState.progress.fraction
|
||||
Icon(
|
||||
@ -453,8 +459,7 @@ fun LibraryViewContent(
|
||||
labels = cardDataWithLabels.labels,
|
||||
highlights = cardDataWithLabels.highlights
|
||||
)
|
||||
SavedItemCard(
|
||||
selected = selected,
|
||||
SavedItemCard(selected = selected,
|
||||
savedItemViewModel = savedItemViewModel,
|
||||
savedItem = savedItem,
|
||||
onClickHandler = {
|
||||
@ -467,11 +472,9 @@ fun LibraryViewContent(
|
||||
},
|
||||
actionHandler = {
|
||||
onSavedItemAction(
|
||||
currentItem.savedItemId,
|
||||
it
|
||||
currentItem.savedItemId, it
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
when {
|
||||
@ -513,9 +516,7 @@ private fun Reset(state: DismissState) {
|
||||
|
||||
@Composable
|
||||
fun InfiniteListHandler(
|
||||
listState: LazyListState,
|
||||
buffer: Int = 2,
|
||||
onLoadMore: () -> Unit
|
||||
listState: LazyListState, buffer: Int = 2, onLoadMore: () -> Unit
|
||||
) {
|
||||
val loadMore = remember {
|
||||
derivedStateOf {
|
||||
@ -528,9 +529,7 @@ fun InfiniteListHandler(
|
||||
}
|
||||
|
||||
LaunchedEffect(loadMore) {
|
||||
snapshotFlow { loadMore.value }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
snapshotFlow { loadMore.value }.distinctUntilChanged().collect {
|
||||
onLoadMore()
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,10 +64,10 @@ class LibraryViewModel @Inject constructor(
|
||||
initialValue = LibraryUiState.Loading
|
||||
)
|
||||
|
||||
val appliedFilterLiveData = MutableLiveData<SavedItemFilter>()
|
||||
val appliedFilterState = MutableStateFlow(SavedItemFilter.INBOX)
|
||||
val appliedSortFilterLiveData = MutableStateFlow(SavedItemSortFilter.NEWEST)
|
||||
val bottomSheetState = MutableStateFlow(LibraryBottomSheetState.HIDDEN)
|
||||
|
||||
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
|
||||
val bottomSheetState = MutableLiveData(LibraryBottomSheetState.HIDDEN)
|
||||
val currentItem = mutableStateOf<String?>(null)
|
||||
|
||||
val labelsState = libraryRepository.getSavedItemsLabels().stateIn(
|
||||
@ -88,7 +88,7 @@ class LibraryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
updateSavedItemFilter(appliedFilterLiveData.value ?: SavedItemFilter.INBOX)
|
||||
updateSavedItemFilter(appliedFilterState.value)
|
||||
}
|
||||
|
||||
private fun syncLabels() {
|
||||
@ -107,6 +107,10 @@ class LibraryViewModel @Inject constructor(
|
||||
load()
|
||||
}
|
||||
|
||||
fun setBottomSheetState(state: LibraryBottomSheetState) {
|
||||
bottomSheetState.value = state
|
||||
}
|
||||
|
||||
private fun getLastSyncTime(): Instant? = runBlocking {
|
||||
datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let {
|
||||
try {
|
||||
@ -155,7 +159,7 @@ class LibraryViewModel @Inject constructor(
|
||||
fun updateSavedItemFilter(filter: SavedItemFilter) {
|
||||
viewModelScope.launch {
|
||||
datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue)
|
||||
appliedFilterLiveData.value = filter
|
||||
appliedFilterState.value = filter
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
@ -178,7 +182,7 @@ class LibraryViewModel @Inject constructor(
|
||||
private fun handleFilterChanges() {
|
||||
librarySearchCursor = null
|
||||
|
||||
if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) {
|
||||
if (appliedSortFilterLiveData.value != null && appliedFilterState.value != null) {
|
||||
val sortKey = when (appliedSortFilterLiveData.value) {
|
||||
SavedItemSortFilter.NEWEST -> "newest"
|
||||
SavedItemSortFilter.OLDEST -> "oldest"
|
||||
@ -187,18 +191,18 @@ class LibraryViewModel @Inject constructor(
|
||||
else -> "newest"
|
||||
}
|
||||
|
||||
val allowedArchiveStates = when (appliedFilterLiveData.value) {
|
||||
val allowedArchiveStates = when (appliedFilterState.value) {
|
||||
SavedItemFilter.ALL -> listOf(0, 1)
|
||||
SavedItemFilter.ARCHIVED -> listOf(1)
|
||||
else -> listOf(0)
|
||||
}
|
||||
|
||||
val allowedContentReaders = when (appliedFilterLiveData.value) {
|
||||
val allowedContentReaders = when (appliedFilterState.value) {
|
||||
SavedItemFilter.FILES -> listOf("PDF", "EPUB")
|
||||
else -> listOf("WEB", "PDF", "EPUB")
|
||||
}
|
||||
|
||||
var requiredLabels = when (appliedFilterLiveData.value) {
|
||||
var requiredLabels = when (appliedFilterState.value) {
|
||||
SavedItemFilter.NEWSLETTERS -> listOf("Newsletter")
|
||||
SavedItemFilter.FEEDS -> listOf("RSS")
|
||||
else -> activeLabels.value.map { it.name }
|
||||
@ -208,9 +212,8 @@ class LibraryViewModel @Inject constructor(
|
||||
requiredLabels = requiredLabels + it.map { it.name }
|
||||
}
|
||||
|
||||
|
||||
val excludeLabels = when (appliedFilterLiveData.value) {
|
||||
SavedItemFilter.READ_LATER -> listOf("Newsletter", "RSS")
|
||||
val excludeLabels = when (appliedFilterState.value) {
|
||||
SavedItemFilter.NON_FEED -> listOf("Newsletter", "RSS")
|
||||
else -> listOf("Newsletter", "RSS")
|
||||
}
|
||||
|
||||
@ -357,7 +360,7 @@ class LibraryViewModel @Inject constructor(
|
||||
|
||||
private fun searchQueryString(): String {
|
||||
var query =
|
||||
"${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
"${appliedFilterState.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
|
||||
activeLabels.value.let {
|
||||
if (it.isNotEmpty()) {
|
||||
|
||||
@ -9,13 +9,11 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
enum class SavedItemFilter(val displayText: String, val rawValue: String, val queryString: String) {
|
||||
FOLLOWING("Following", "following", "in:following use:folders"),
|
||||
INBOX("Inbox", rawValue = "inbox", "in:inbox use:folders"),
|
||||
READ_LATER("Non-Feed Items", "nonFeed", "no:subscription"),
|
||||
NON_FEED("Non-Feed Items", "nonFeed", "no:subscription"),
|
||||
FEEDS("Feeds", "feeds", "in:inbox label:RSS"),
|
||||
NEWSLETTERS("Newsletters", "newsletters", "in:inbox label:Newsletter"),
|
||||
ALL("All", "all", "in:all"),
|
||||
ARCHIVED("Archived", "archived", "in:archive"),
|
||||
|
||||
// HAS_HIGHLIGHTS("Highlighted", "hasHighlights", "has:highlights"),
|
||||
FILES("Files", "files", "type:file"),
|
||||
}
|
||||
|
||||
@ -35,7 +33,7 @@ fun SavedItemFilterContextMenu(
|
||||
} else {
|
||||
listOf(
|
||||
SavedItemFilter.INBOX,
|
||||
SavedItemFilter.READ_LATER,
|
||||
SavedItemFilter.NON_FEED,
|
||||
SavedItemFilter.ALL,
|
||||
SavedItemFilter.ARCHIVED,
|
||||
SavedItemFilter.FILES
|
||||
|
||||
Reference in New Issue
Block a user