diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/dao/SavedItemDao.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/dao/SavedItemDao.kt index c041cc721..e8463717e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/dao/SavedItemDao.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/dao/SavedItemDao.kt @@ -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) @@ -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 " + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingScreen.kt index 343702d68..f4d36100f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingScreen.kt @@ -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, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt index 82d23ceaa..145a3a99d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/following/FollowingViewModel.kt @@ -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(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()) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryFilterBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryFilterBar.kt index 129df6fc8..fa02563ce 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryFilterBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryFilterBar.kt @@ -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, + setBottomSheetState: (LibraryBottomSheetState) -> Unit, + updateSavedItemFilter: (SavedItemFilter) -> Unit, + updateSavedItemSortFilter: (SavedItemSortFilter) -> Unit, + updateAppliedLabels: (List) -> Unit ) { var isSavedItemFilterMenuExpanded by remember { mutableStateOf(false) } - val activeSavedItemFilter: SavedItemFilter by viewModel.appliedFilterLiveData.observeAsState( - if (isFollowingScreen) SavedItemFilter.FOLLOWING else SavedItemFilter.INBOX - ) - val activeLabels: List 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) }) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt index 89c34f535..cfeb458a0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt @@ -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, + sortFilter: SavedItemSortFilter, + updateSavedItemFilter:(SavedItemFilter) -> Unit, + updateSavedItemSortFilter: (SavedItemSortFilter) -> Unit, + setBottomSheetState: (LibraryBottomSheetState) -> Unit, + updateAppliedLabels: (List) -> 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() } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt index 3d805ba4c..b4d4d1198 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryViewModel.kt @@ -64,10 +64,10 @@ class LibraryViewModel @Inject constructor( initialValue = LibraryUiState.Loading ) - val appliedFilterLiveData = MutableLiveData() + 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(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()) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/SavedItemFilter.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/SavedItemFilter.kt index a829e03ad..89d0c0d3d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/SavedItemFilter.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/SavedItemFilter.kt @@ -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