separate following screen viewmodel
This commit is contained in:
@ -0,0 +1,153 @@
|
||||
package app.omnivore.omnivore.feature.following
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.feature.components.LabelsViewModel
|
||||
import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
|
||||
import app.omnivore.omnivore.feature.library.AddLinkBottomSheet
|
||||
import app.omnivore.omnivore.feature.library.EditBottomSheet
|
||||
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.save.SaveViewModel
|
||||
import app.omnivore.omnivore.navigation.Routes
|
||||
import app.omnivore.omnivore.navigation.TopLevelDestination
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun FollowingScreen(
|
||||
labelsViewModel: LabelsViewModel,
|
||||
saveViewModel: SaveViewModel,
|
||||
editInfoViewModel: EditInfoViewModel,
|
||||
navController: NavHostController,
|
||||
viewModel: FollowingViewModel = hiltViewModel()
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
val showBottomSheet: LibraryBottomSheetState by viewModel.bottomSheetState.observeAsState(
|
||||
LibraryBottomSheetState.HIDDEN
|
||||
)
|
||||
|
||||
viewModel.snackbarMessage?.let {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
viewModel.clearSnackbarMessage()
|
||||
}
|
||||
}
|
||||
|
||||
val labels by viewModel.labelsState.collectAsStateWithLifecycle()
|
||||
val activeLabels by viewModel.activeLabels.collectAsStateWithLifecycle()
|
||||
|
||||
when (showBottomSheet) {
|
||||
LibraryBottomSheetState.ADD_LINK -> {
|
||||
AddLinkBottomSheet(saveViewModel) {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN
|
||||
}
|
||||
}
|
||||
|
||||
LibraryBottomSheetState.LABEL -> {
|
||||
LabelBottomSheet(
|
||||
deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
labels = labels,
|
||||
currentSavedItemData = viewModel.currentSavedItemUnderEdit(),
|
||||
labelsViewModel,
|
||||
{ viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN },
|
||||
{ labelName, hexColorValue ->
|
||||
viewModel.createNewSavedItemLabel(labelName, hexColorValue)
|
||||
},
|
||||
{ savedItemId, labels ->
|
||||
viewModel.updateSavedItemLabels(savedItemId, labels)
|
||||
},
|
||||
activeLabels,
|
||||
{ viewModel.updateAppliedLabels(it) }
|
||||
)
|
||||
}
|
||||
|
||||
LibraryBottomSheetState.EDIT -> {
|
||||
EditBottomSheet(
|
||||
editInfoViewModel,
|
||||
deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
{ viewModel.refresh() },
|
||||
viewModel.currentSavedItemUnderEdit()
|
||||
) {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN
|
||||
}
|
||||
}
|
||||
|
||||
LibraryBottomSheetState.HIDDEN -> {
|
||||
}
|
||||
}
|
||||
|
||||
val currentTopLevelDestination = TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
LibraryNavigationBar(
|
||||
currentDestination = currentTopLevelDestination,
|
||||
savedItemViewModel = viewModel,
|
||||
onSearchClicked = { navController.navigate(Routes.Search.route) },
|
||||
onAddLinkClicked = {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK
|
||||
}
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
when (uiState) {
|
||||
is FollowingUiState.Success -> {
|
||||
LibraryViewContent(
|
||||
isFollowingScreen = currentTopLevelDestination == TopLevelDestination.FOLLOWING,
|
||||
{ viewModel.actionsMenuItemLiveData.postValue(null) },
|
||||
savedItemViewModel = viewModel,
|
||||
refresh = { viewModel.refresh() },
|
||||
onUnarchive = { viewModel.unarchiveSavedItem(it) },
|
||||
onArchive = { viewModel.archiveSavedItem(it) },
|
||||
onDelete = { viewModel.deleteSavedItem(it) },
|
||||
paddingValues = paddingValues,
|
||||
items = (uiState as FollowingUiState.Success).items,
|
||||
selectedItem = selectedItem,
|
||||
onSavedItemAction = { id, action ->
|
||||
viewModel.handleSavedItemAction(id, action)
|
||||
},
|
||||
{ viewModel.loadUsingSearchAPI() },
|
||||
{ viewModel.initialLoad() }
|
||||
)
|
||||
}
|
||||
is FollowingUiState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(strokeCap = StrokeCap.Round)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,390 @@
|
||||
package app.omnivore.omnivore.feature.following
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.core.data.model.LibraryQuery
|
||||
import app.omnivore.omnivore.core.data.repository.LibraryRepository
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.core.datastore.DatastoreRepository
|
||||
import app.omnivore.omnivore.feature.library.LibraryBottomSheetState
|
||||
import app.omnivore.omnivore.feature.library.SavedItemAction
|
||||
import app.omnivore.omnivore.feature.library.SavedItemFilter
|
||||
import app.omnivore.omnivore.feature.library.SavedItemSortFilter
|
||||
import app.omnivore.omnivore.feature.library.SavedItemViewModel
|
||||
import app.omnivore.omnivore.utils.DatastoreKeys
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltViewModel
|
||||
class FollowingViewModel @Inject constructor(
|
||||
private val datastoreRepo: DatastoreRepository,
|
||||
private val libraryRepository: LibraryRepository,
|
||||
@ApplicationContext private val applicationContext: Context
|
||||
) : ViewModel(), SavedItemViewModel {
|
||||
|
||||
private val contentRequestChannel = Channel<String>(capacity = Channel.UNLIMITED)
|
||||
private var librarySearchCursor: String? = null
|
||||
|
||||
var snackbarMessage by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
private val _libraryQuery = MutableStateFlow(
|
||||
LibraryQuery(
|
||||
allowedArchiveStates = listOf(0),
|
||||
sortKey = "newest",
|
||||
requiredLabels = listOf(),
|
||||
excludedLabels = listOf(),
|
||||
allowedContentReaders = listOf("WEB", "PDF", "EPUB")
|
||||
)
|
||||
)
|
||||
|
||||
val uiState: StateFlow<FollowingUiState> = _libraryQuery.flatMapLatest { query ->
|
||||
libraryRepository.getSavedItems(query)
|
||||
}.map(FollowingUiState::Success).stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = FollowingUiState.Loading
|
||||
)
|
||||
|
||||
val appliedFilterLiveData = MutableLiveData<SavedItemFilter>(
|
||||
SavedItemFilter.FOLLOWING
|
||||
)
|
||||
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
|
||||
val bottomSheetState = MutableLiveData(LibraryBottomSheetState.HIDDEN)
|
||||
val currentItem = mutableStateOf<String?>(null)
|
||||
|
||||
val labelsState = libraryRepository.getSavedItemsLabels().stateIn(
|
||||
scope = viewModelScope, started = SharingStarted.Lazily, initialValue = listOf()
|
||||
)
|
||||
|
||||
val activeLabels = MutableStateFlow<List<SavedItemLabel>>(listOf())
|
||||
|
||||
override val actionsMenuItemLiveData = MutableLiveData<SavedItemWithLabelsAndHighlights?>(null)
|
||||
|
||||
|
||||
private fun loadInitialFilterValues() {
|
||||
syncLabels()
|
||||
|
||||
viewModelScope.launch {
|
||||
handleFilterChanges()
|
||||
for (slug in contentRequestChannel) {
|
||||
libraryRepository.fetchSavedItemContent(slug)
|
||||
}
|
||||
}
|
||||
|
||||
updateSavedItemFilter(appliedFilterLiveData.value ?: SavedItemFilter.INBOX)
|
||||
}
|
||||
|
||||
private fun syncLabels() {
|
||||
viewModelScope.launch {
|
||||
val labels = libraryRepository.getLabels()
|
||||
libraryRepository.insertAllLabels(labels)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearSnackbarMessage() {
|
||||
snackbarMessage = null
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
librarySearchCursor = null
|
||||
load()
|
||||
}
|
||||
|
||||
private fun getLastSyncTime(): Instant? = runBlocking {
|
||||
datastoreRepo.getString(DatastoreKeys.libraryLastSyncTimestamp)?.let {
|
||||
try {
|
||||
return@let Instant.parse(it)
|
||||
} catch (e: Exception) {
|
||||
return@let null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initialLoad() {
|
||||
if (getLastSyncTime() == null) {
|
||||
librarySearchCursor = null
|
||||
}
|
||||
load()
|
||||
}
|
||||
|
||||
fun load() {
|
||||
loadInitialFilterValues()
|
||||
|
||||
viewModelScope.launch {
|
||||
syncItems()
|
||||
loadUsingSearchAPI()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadUsingSearchAPI() {
|
||||
viewModelScope.launch {
|
||||
val result = libraryRepository.librarySearch(
|
||||
cursor = librarySearchCursor, query = searchQueryString()
|
||||
)
|
||||
result.cursor?.let {
|
||||
librarySearchCursor = it
|
||||
}
|
||||
result.savedItems.map {
|
||||
val isSavedInDB = libraryRepository.isSavedItemContentStoredInDB(it.savedItem.slug)
|
||||
|
||||
if (!isSavedInDB) {
|
||||
delay(2000)
|
||||
contentRequestChannel.send(it.savedItem.slug)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSavedItemFilter(filter: SavedItemFilter) {
|
||||
viewModelScope.launch {
|
||||
datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemFilter, filter.rawValue)
|
||||
appliedFilterLiveData.value = filter
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSavedItemSortFilter(filter: SavedItemSortFilter) {
|
||||
viewModelScope.launch {
|
||||
datastoreRepo.putString(DatastoreKeys.lastUsedSavedItemSortFilter, filter.rawValue)
|
||||
appliedSortFilterLiveData.value = filter
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAppliedLabels(labels: List<SavedItemLabel>) {
|
||||
viewModelScope.launch {
|
||||
activeLabels.value = labels
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
|
||||
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.FOLLOWING -> listOf("Newsletter", "RSS")
|
||||
SavedItemFilter.NEWSLETTERS -> listOf("Newsletter")
|
||||
SavedItemFilter.FEEDS -> listOf("RSS")
|
||||
else -> listOf("Newsletter", "RSS")//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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun syncItems() {
|
||||
val syncStart = Instant.now()
|
||||
val lastSyncDate = getLastSyncTime() ?: Instant.MIN
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
performItemSync(
|
||||
cursor = null,
|
||||
since = lastSyncDate.toString(),
|
||||
count = 0,
|
||||
startTime = syncStart.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun performItemSync(
|
||||
cursor: String?,
|
||||
since: String,
|
||||
count: Int,
|
||||
startTime: String,
|
||||
isInitialBatch: Boolean = true
|
||||
) {
|
||||
libraryRepository.syncOfflineItemsWithServerIfNeeded()
|
||||
val result = libraryRepository.sync(since = since, cursor = cursor, limit = 20)
|
||||
|
||||
// Fetch content for the initial batch only
|
||||
if (isInitialBatch) {
|
||||
for (slug in result.savedItemSlugs) {
|
||||
delay(250)
|
||||
contentRequestChannel.send(slug)
|
||||
}
|
||||
}
|
||||
|
||||
val totalCount = count + result.count
|
||||
|
||||
if (!result.hasError && result.hasMoreItems && result.cursor != null) {
|
||||
performItemSync(
|
||||
cursor = result.cursor,
|
||||
since = since,
|
||||
count = totalCount,
|
||||
startTime = startTime,
|
||||
isInitialBatch = false
|
||||
)
|
||||
} else {
|
||||
datastoreRepo.putString(DatastoreKeys.libraryLastSyncTimestamp, startTime)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleSavedItemAction(itemId: String, action: SavedItemAction) {
|
||||
when (action) {
|
||||
SavedItemAction.Delete -> {
|
||||
deleteSavedItem(itemId)
|
||||
}
|
||||
|
||||
SavedItemAction.Archive -> {
|
||||
archiveSavedItem(itemId)
|
||||
}
|
||||
|
||||
SavedItemAction.Unarchive -> {
|
||||
unarchiveSavedItem(itemId)
|
||||
}
|
||||
|
||||
SavedItemAction.EditLabels -> {
|
||||
currentItem.value = itemId
|
||||
bottomSheetState.value = LibraryBottomSheetState.LABEL
|
||||
}
|
||||
|
||||
SavedItemAction.EditInfo -> {
|
||||
currentItem.value = itemId
|
||||
bottomSheetState.value = LibraryBottomSheetState.EDIT
|
||||
}
|
||||
|
||||
SavedItemAction.MarkRead -> {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.updateReadingProgress(itemId, 100.0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
SavedItemAction.MarkUnread -> {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.updateReadingProgress(itemId, 0.0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
actionsMenuItemLiveData.postValue(null)
|
||||
}
|
||||
|
||||
fun deleteSavedItem(itemID: String) {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.deleteSavedItem(itemID)
|
||||
}
|
||||
}
|
||||
|
||||
fun archiveSavedItem(itemID: String) {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.archiveSavedItem(itemID)
|
||||
}
|
||||
}
|
||||
|
||||
fun unarchiveSavedItem(itemID: String) {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.unarchiveSavedItem(itemID)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
|
||||
viewModelScope.launch {
|
||||
val result = libraryRepository.setSavedItemLabels(
|
||||
itemId = savedItemID, labels = labels
|
||||
)
|
||||
snackbarMessage = if (result) {
|
||||
applicationContext.getString(R.string.library_view_model_snackbar_success)
|
||||
} else {
|
||||
applicationContext.getString(R.string.library_view_model_snackbar_error)
|
||||
}
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
|
||||
fun createNewSavedItemLabel(labelName: String, hexColorValue: String) {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.createNewSavedItemLabel(labelName, hexColorValue)
|
||||
}
|
||||
}
|
||||
|
||||
fun currentSavedItemUnderEdit(): SavedItemWithLabelsAndHighlights? {
|
||||
currentItem.value?.let { itemID ->
|
||||
return (uiState.value as FollowingUiState.Success).items.first { it.savedItem.savedItemId == itemID }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun searchQueryString(): String {
|
||||
var query =
|
||||
"${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
|
||||
activeLabels.value.let {
|
||||
if (it.isNotEmpty()) {
|
||||
query += " label:"
|
||||
query += it.joinToString { label -> label.name }
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface FollowingUiState {
|
||||
data object Loading : FollowingUiState
|
||||
|
||||
data class Success(
|
||||
val items: List<SavedItemWithLabelsAndHighlights>,
|
||||
) : FollowingUiState
|
||||
|
||||
data object Error : FollowingUiState
|
||||
}
|
||||
@ -27,6 +27,7 @@ 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
|
||||
@ -41,7 +42,7 @@ fun LibraryFilterBar(
|
||||
val activeSavedItemFilter: SavedItemFilter by viewModel.appliedFilterLiveData.observeAsState(
|
||||
if (isFollowingScreen) SavedItemFilter.FOLLOWING else SavedItemFilter.INBOX
|
||||
)
|
||||
val activeLabels: List<SavedItemLabel> by viewModel.activeLabelsLiveData.observeAsState(listOf())
|
||||
val activeLabels: List<SavedItemLabel> by viewModel.activeLabels.collectAsStateWithLifecycle()
|
||||
|
||||
var isSavedItemSortFilterMenuExpanded by remember { mutableStateOf(false) }
|
||||
val activeSavedItemSortFilter: SavedItemSortFilter by viewModel.appliedSortFilterLiveData.observeAsState(
|
||||
@ -97,7 +98,7 @@ fun LibraryFilterBar(
|
||||
val chipColors = LabelChipColors.fromHex(label.color)
|
||||
|
||||
AssistChip(onClick = {
|
||||
viewModel.updateAppliedLabels((viewModel.activeLabelsLiveData.value
|
||||
viewModel.updateAppliedLabels((viewModel.activeLabels.value
|
||||
?: listOf()).filter { it.savedItemLabelId != label.savedItemLabelId })
|
||||
},
|
||||
label = { Text(label.name) },
|
||||
|
||||
@ -21,14 +21,12 @@ import androidx.compose.material.DismissState
|
||||
import androidx.compose.material.DismissValue
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FractionalThreshold
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.material.SwipeToDismiss
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Archive
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Unarchive
|
||||
import androidx.compose.material.rememberDismissState
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@ -36,6 +34,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
@ -60,6 +59,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
|
||||
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.feature.components.AddLinkSheetContent
|
||||
import app.omnivore.omnivore.feature.components.LabelsSelectionSheetContent
|
||||
@ -85,8 +85,7 @@ internal fun LibraryView(
|
||||
navController: NavHostController,
|
||||
viewModel: LibraryViewModel = hiltViewModel()
|
||||
) {
|
||||
val scaffoldState: ScaffoldState = rememberScaffoldState()
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
@ -97,11 +96,14 @@ internal fun LibraryView(
|
||||
|
||||
viewModel.snackbarMessage?.let {
|
||||
coroutineScope.launch {
|
||||
scaffoldState.snackbarHostState.showSnackbar(it)
|
||||
snackbarHostState.showSnackbar(it)
|
||||
viewModel.clearSnackbarMessage()
|
||||
}
|
||||
}
|
||||
|
||||
val labels by viewModel.labelsState.collectAsStateWithLifecycle()
|
||||
val activeLabels by viewModel.activeLabels.collectAsStateWithLifecycle()
|
||||
|
||||
when (showBottomSheet) {
|
||||
LibraryBottomSheetState.ADD_LINK -> {
|
||||
AddLinkBottomSheet(saveViewModel) {
|
||||
@ -111,17 +113,28 @@ internal fun LibraryView(
|
||||
|
||||
LibraryBottomSheetState.LABEL -> {
|
||||
LabelBottomSheet(
|
||||
viewModel,
|
||||
labelsViewModel
|
||||
) {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN
|
||||
}
|
||||
deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
labels = labels,
|
||||
currentSavedItemData = viewModel.currentSavedItemUnderEdit(),
|
||||
labelsViewModel,
|
||||
{ viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN },
|
||||
{ labelName, hexColorValue ->
|
||||
viewModel.createNewSavedItemLabel(labelName, hexColorValue)
|
||||
},
|
||||
{ savedItemId, labels ->
|
||||
viewModel.updateSavedItemLabels(savedItemId, labels)
|
||||
},
|
||||
activeLabels,
|
||||
{ viewModel.updateAppliedLabels(it) }
|
||||
)
|
||||
}
|
||||
|
||||
LibraryBottomSheetState.EDIT -> {
|
||||
EditBottomSheet(
|
||||
editInfoViewModel,
|
||||
viewModel
|
||||
deleteCurrentItem = { viewModel.currentItem.value = null },
|
||||
{ viewModel.refresh() },
|
||||
viewModel.currentSavedItemUnderEdit()
|
||||
) {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.HIDDEN
|
||||
}
|
||||
@ -132,6 +145,7 @@ internal fun LibraryView(
|
||||
}
|
||||
|
||||
val currentTopLevelDestination = TopLevelDestination.entries.find { it.route == navController.currentDestination?.route }
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@ -139,7 +153,9 @@ internal fun LibraryView(
|
||||
currentDestination = currentTopLevelDestination,
|
||||
savedItemViewModel = viewModel,
|
||||
onSearchClicked = { navController.navigate(Routes.Search.route) },
|
||||
onAddLinkClicked = { showAddLinkBottomSheet(viewModel) }
|
||||
onAddLinkClicked = {
|
||||
viewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK
|
||||
}
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
@ -147,9 +163,20 @@ internal fun LibraryView(
|
||||
is LibraryUiState.Success -> {
|
||||
LibraryViewContent(
|
||||
isFollowingScreen = currentTopLevelDestination == TopLevelDestination.FOLLOWING,
|
||||
viewModel,
|
||||
{ viewModel.actionsMenuItemLiveData.postValue(null) },
|
||||
savedItemViewModel = viewModel,
|
||||
refresh = { viewModel.refresh() },
|
||||
onUnarchive = { viewModel.unarchiveSavedItem(it) },
|
||||
onArchive = { viewModel.archiveSavedItem(it) },
|
||||
onDelete = { viewModel.deleteSavedItem(it) },
|
||||
paddingValues = paddingValues,
|
||||
uiState = uiState
|
||||
items = (uiState as LibraryUiState.Success).items,
|
||||
selectedItem = selectedItem,
|
||||
onSavedItemAction = { id, action ->
|
||||
viewModel.handleSavedItemAction(id, action)
|
||||
},
|
||||
{ viewModel.loadUsingSearchAPI() },
|
||||
{ viewModel.initialLoad() }
|
||||
)
|
||||
}
|
||||
is LibraryUiState.Loading -> {
|
||||
@ -169,16 +196,18 @@ internal fun LibraryView(
|
||||
}
|
||||
}
|
||||
|
||||
fun showAddLinkBottomSheet(libraryViewModel: LibraryViewModel) {
|
||||
libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LabelBottomSheet(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
deleteCurrentItem: () -> Unit,
|
||||
labels: List<SavedItemLabel>,
|
||||
currentSavedItemData: SavedItemWithLabelsAndHighlights?,
|
||||
labelsViewModel: LabelsViewModel,
|
||||
onDismiss: () -> Unit = {}
|
||||
onDismiss: () -> Unit = {},
|
||||
createNewSavedItemLabel: (String, String) -> Unit,
|
||||
updateSavedItemLabels: (String, List<SavedItemLabel>) -> Unit,
|
||||
activeLabels: List<SavedItemLabel>,
|
||||
updateAppliedLabels: (List<SavedItemLabel>) -> Unit
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { onDismiss() },
|
||||
@ -188,48 +217,41 @@ fun LabelBottomSheet(
|
||||
),
|
||||
) {
|
||||
|
||||
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
|
||||
|
||||
val labels by libraryViewModel.labelsState.collectAsStateWithLifecycle()
|
||||
|
||||
if (currentSavedItemData != null) {
|
||||
LabelsSelectionSheetContent(
|
||||
labels = labels,
|
||||
labelsViewModel = labelsViewModel,
|
||||
initialSelectedLabels = currentSavedItemData.labels,
|
||||
onCancel = {
|
||||
libraryViewModel.currentItem.value = null
|
||||
deleteCurrentItem()
|
||||
onDismiss()
|
||||
},
|
||||
isLibraryMode = false,
|
||||
onSave = {
|
||||
if (it != labels) {
|
||||
libraryViewModel.updateSavedItemLabels(
|
||||
savedItemID = currentSavedItemData.savedItem.savedItemId,
|
||||
labels = it
|
||||
)
|
||||
updateSavedItemLabels(currentSavedItemData.savedItem.savedItemId, it)
|
||||
}
|
||||
libraryViewModel.currentItem.value = null
|
||||
deleteCurrentItem()
|
||||
onDismiss()
|
||||
},
|
||||
onCreateLabel = { newLabelName, labelHexValue ->
|
||||
libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
|
||||
createNewSavedItemLabel(newLabelName, labelHexValue)
|
||||
}
|
||||
)
|
||||
} else { // Is used in library mode
|
||||
LabelsSelectionSheetContent(
|
||||
labels = labels,
|
||||
labelsViewModel = labelsViewModel,
|
||||
initialSelectedLabels = libraryViewModel.activeLabelsLiveData.value ?: listOf(),
|
||||
initialSelectedLabels = activeLabels,
|
||||
onCancel = { onDismiss() },
|
||||
isLibraryMode = true,
|
||||
onSave = {
|
||||
libraryViewModel.updateAppliedLabels(it)
|
||||
libraryViewModel.currentItem.value = null
|
||||
updateAppliedLabels(it)
|
||||
deleteCurrentItem()
|
||||
onDismiss()
|
||||
},
|
||||
onCreateLabel = { newLabelName, labelHexValue ->
|
||||
libraryViewModel.createNewSavedItemLabel(newLabelName, labelHexValue)
|
||||
createNewSavedItemLabel(newLabelName, labelHexValue)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -268,7 +290,9 @@ fun AddLinkBottomSheet(
|
||||
@Composable
|
||||
fun EditBottomSheet(
|
||||
editInfoViewModel: EditInfoViewModel,
|
||||
libraryViewModel: LibraryViewModel,
|
||||
deleteCurrentItem: () -> Unit,
|
||||
refresh: () -> Unit,
|
||||
currentSavedItemUnderEdit: SavedItemWithLabelsAndHighlights?,
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
@ -278,20 +302,19 @@ fun EditBottomSheet(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
) {
|
||||
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
|
||||
EditInfoSheetContent(
|
||||
savedItemId = currentSavedItemData?.savedItem?.savedItemId,
|
||||
title = currentSavedItemData?.savedItem?.title,
|
||||
author = currentSavedItemData?.savedItem?.author,
|
||||
description = currentSavedItemData?.savedItem?.descriptionText,
|
||||
savedItemId = currentSavedItemUnderEdit?.savedItem?.savedItemId,
|
||||
title = currentSavedItemUnderEdit?.savedItem?.title,
|
||||
author = currentSavedItemUnderEdit?.savedItem?.author,
|
||||
description = currentSavedItemUnderEdit?.savedItem?.descriptionText,
|
||||
viewModel = editInfoViewModel,
|
||||
onCancel = {
|
||||
libraryViewModel.currentItem.value = null
|
||||
deleteCurrentItem()
|
||||
onDismiss()
|
||||
},
|
||||
onUpdated = {
|
||||
libraryViewModel.currentItem.value = null
|
||||
libraryViewModel.refresh()
|
||||
deleteCurrentItem()
|
||||
refresh()
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
@ -303,9 +326,18 @@ fun EditBottomSheet(
|
||||
@Composable
|
||||
fun LibraryViewContent(
|
||||
isFollowingScreen: Boolean,
|
||||
libraryViewModel: LibraryViewModel,
|
||||
selectItem: () -> Unit,
|
||||
savedItemViewModel: SavedItemViewModel,
|
||||
refresh: () -> Unit,
|
||||
onUnarchive: (String) -> Unit,
|
||||
onArchive: (String) -> Unit,
|
||||
onDelete: (String) -> Unit,
|
||||
paddingValues: PaddingValues,
|
||||
uiState: LibraryUiState
|
||||
items: List<SavedItemWithLabelsAndHighlights>,
|
||||
selectedItem: SavedItemWithLabelsAndHighlights?,
|
||||
onSavedItemAction: (String, SavedItemAction) -> Unit,
|
||||
loadUsingSearchAPI: () -> Unit,
|
||||
initialLoad: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
@ -315,13 +347,11 @@ fun LibraryViewContent(
|
||||
LaunchedEffect(true) {
|
||||
// fetch something
|
||||
delay(1500)
|
||||
libraryViewModel.refresh()
|
||||
refresh()
|
||||
pullToRefreshState.endRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
val selectedItem: SavedItemWithLabelsAndHighlights? by libraryViewModel.actionsMenuItemLiveData.observeAsState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
@ -337,11 +367,10 @@ fun LibraryViewContent(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
items(
|
||||
items = (uiState as LibraryUiState.Success).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(
|
||||
@ -364,12 +393,12 @@ fun LibraryViewContent(
|
||||
|
||||
if (it == DismissValue.DismissedToEnd) { // Archiving/UnArchiving.
|
||||
if (currentItem.isArchived) {
|
||||
libraryViewModel.unarchiveSavedItem(currentItem.savedItemId)
|
||||
onUnarchive(currentItem.savedItemId)
|
||||
} else {
|
||||
libraryViewModel.archiveSavedItem(currentItem.savedItemId)
|
||||
onArchive(currentItem.savedItemId)
|
||||
}
|
||||
} else if (it == DismissValue.DismissedToStart) { // Deleting.
|
||||
libraryViewModel.deleteSavedItem(currentItem.savedItemId)
|
||||
onDelete(currentItem.savedItemId)
|
||||
}
|
||||
|
||||
true
|
||||
@ -426,10 +455,10 @@ fun LibraryViewContent(
|
||||
)
|
||||
SavedItemCard(
|
||||
selected = selected,
|
||||
savedItemViewModel = libraryViewModel,
|
||||
savedItemViewModel = savedItemViewModel,
|
||||
savedItem = savedItem,
|
||||
onClickHandler = {
|
||||
libraryViewModel.actionsMenuItemLiveData.postValue(null)
|
||||
selectItem()
|
||||
val activityClass =
|
||||
if (currentItem.contentReader == "PDF") PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java
|
||||
val intent = Intent(context, activityClass)
|
||||
@ -437,7 +466,7 @@ fun LibraryViewContent(
|
||||
context.startActivity(intent)
|
||||
},
|
||||
actionHandler = {
|
||||
libraryViewModel.handleSavedItemAction(
|
||||
onSavedItemAction(
|
||||
currentItem.savedItemId,
|
||||
it
|
||||
)
|
||||
@ -454,12 +483,12 @@ fun LibraryViewContent(
|
||||
}
|
||||
|
||||
InfiniteListHandler(listState = listState) {
|
||||
if ((uiState as LibraryUiState.Success).items.isEmpty()) {
|
||||
if (items.isEmpty()) {
|
||||
Log.d("sync", "loading with load func")
|
||||
libraryViewModel.initialLoad()
|
||||
initialLoad()
|
||||
} else {
|
||||
Log.d("sync", "loading with search api")
|
||||
libraryViewModel.loadUsingSearchAPI()
|
||||
loadUsingSearchAPI()
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,8 +496,6 @@ fun LibraryViewContent(
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
state = pullToRefreshState,
|
||||
)
|
||||
|
||||
// LabelsSelectionSheet(viewModel = libraryViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ class LibraryViewModel @Inject constructor(
|
||||
|
||||
var snackbarMessage by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
|
||||
private val _libraryQuery = MutableStateFlow(
|
||||
LibraryQuery(
|
||||
allowedArchiveStates = listOf(0),
|
||||
@ -59,10 +59,10 @@ class LibraryViewModel @Inject constructor(
|
||||
val uiState: StateFlow<LibraryUiState> = _libraryQuery.flatMapLatest { query ->
|
||||
libraryRepository.getSavedItems(query)
|
||||
}.map(LibraryUiState::Success).stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = LibraryUiState.Loading
|
||||
)
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = LibraryUiState.Loading
|
||||
)
|
||||
|
||||
val appliedFilterLiveData = MutableLiveData<SavedItemFilter>()
|
||||
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
|
||||
@ -73,7 +73,7 @@ class LibraryViewModel @Inject constructor(
|
||||
scope = viewModelScope, started = SharingStarted.Lazily, initialValue = listOf()
|
||||
)
|
||||
|
||||
val activeLabelsLiveData = MutableLiveData<List<SavedItemLabel>>(listOf())
|
||||
val activeLabels = MutableStateFlow<List<SavedItemLabel>>(listOf())
|
||||
|
||||
override val actionsMenuItemLiveData = MutableLiveData<SavedItemWithLabelsAndHighlights?>(null)
|
||||
|
||||
@ -169,7 +169,7 @@ class LibraryViewModel @Inject constructor(
|
||||
|
||||
fun updateAppliedLabels(labels: List<SavedItemLabel>) {
|
||||
viewModelScope.launch {
|
||||
activeLabelsLiveData.value = labels
|
||||
activeLabels.value = labels
|
||||
handleFilterChanges()
|
||||
}
|
||||
}
|
||||
@ -201,10 +201,10 @@ class LibraryViewModel @Inject constructor(
|
||||
SavedItemFilter.FOLLOWING -> listOf("Newsletter", "RSS")
|
||||
SavedItemFilter.NEWSLETTERS -> listOf("Newsletter")
|
||||
SavedItemFilter.FEEDS -> listOf("RSS")
|
||||
else -> (activeLabelsLiveData.value ?: listOf()).map { it.name }
|
||||
else -> activeLabels.value.map { it.name }
|
||||
}
|
||||
|
||||
activeLabelsLiveData.value?.let { it ->
|
||||
activeLabels.value.let { it ->
|
||||
requiredLabels = requiredLabels + it.map { it.name }
|
||||
}
|
||||
|
||||
@ -271,39 +271,39 @@ class LibraryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleSavedItemAction(itemID: String, action: SavedItemAction) {
|
||||
override fun handleSavedItemAction(itemId: String, action: SavedItemAction) {
|
||||
when (action) {
|
||||
SavedItemAction.Delete -> {
|
||||
deleteSavedItem(itemID)
|
||||
deleteSavedItem(itemId)
|
||||
}
|
||||
|
||||
SavedItemAction.Archive -> {
|
||||
archiveSavedItem(itemID)
|
||||
archiveSavedItem(itemId)
|
||||
}
|
||||
|
||||
SavedItemAction.Unarchive -> {
|
||||
unarchiveSavedItem(itemID)
|
||||
unarchiveSavedItem(itemId)
|
||||
}
|
||||
|
||||
SavedItemAction.EditLabels -> {
|
||||
currentItem.value = itemID
|
||||
currentItem.value = itemId
|
||||
bottomSheetState.value = LibraryBottomSheetState.LABEL
|
||||
}
|
||||
|
||||
SavedItemAction.EditInfo -> {
|
||||
currentItem.value = itemID
|
||||
currentItem.value = itemId
|
||||
bottomSheetState.value = LibraryBottomSheetState.EDIT
|
||||
}
|
||||
|
||||
SavedItemAction.MarkRead -> {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.updateReadingProgress(itemID, 100.0, 0)
|
||||
libraryRepository.updateReadingProgress(itemId, 100.0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
SavedItemAction.MarkUnread -> {
|
||||
viewModelScope.launch {
|
||||
libraryRepository.updateReadingProgress(itemID, 0.0, 0)
|
||||
libraryRepository.updateReadingProgress(itemId, 0.0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -328,10 +328,10 @@ class LibraryViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSavedItemLabels(savedItemID: String, labels: List<SavedItemLabel>) {
|
||||
fun updateSavedItemLabels(savedItemId: String, labels: List<SavedItemLabel>) {
|
||||
viewModelScope.launch {
|
||||
val result = libraryRepository.setSavedItemLabels(
|
||||
itemId = savedItemID, labels = labels
|
||||
itemId = savedItemId, labels = labels
|
||||
)
|
||||
snackbarMessage = if (result) {
|
||||
applicationContext.getString(R.string.library_view_model_snackbar_success)
|
||||
@ -352,7 +352,6 @@ class LibraryViewModel @Inject constructor(
|
||||
currentItem.value?.let { itemID ->
|
||||
return (uiState.value as LibraryUiState.Success).items.first { it.savedItem.savedItemId == itemID }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -360,7 +359,7 @@ class LibraryViewModel @Inject constructor(
|
||||
var query =
|
||||
"${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
|
||||
activeLabelsLiveData.value?.let {
|
||||
activeLabels.value.let {
|
||||
if (it.isNotEmpty()) {
|
||||
query += " label:"
|
||||
query += it.joinToString { label -> label.name }
|
||||
|
||||
@ -8,5 +8,5 @@ interface SavedItemViewModel {
|
||||
val actionsMenuItemLiveData: MutableLiveData<SavedItemWithLabelsAndHighlights?>
|
||||
get() = MutableLiveData<SavedItemWithLabelsAndHighlights?>(null)
|
||||
|
||||
fun handleSavedItemAction(itemID: String, action: SavedItemAction)
|
||||
fun handleSavedItemAction(itemId: String, action: SavedItemAction)
|
||||
}
|
||||
|
||||
@ -153,23 +153,23 @@ class SearchViewModel @Inject constructor(
|
||||
isRefreshing.postValue(false)
|
||||
}
|
||||
|
||||
override fun handleSavedItemAction(itemID: String, action: SavedItemAction) {
|
||||
override fun handleSavedItemAction(itemId: String, action: SavedItemAction) {
|
||||
when (action) {
|
||||
SavedItemAction.Delete -> {
|
||||
viewModelScope.launch {
|
||||
dataService.deleteSavedItem(itemID)
|
||||
dataService.deleteSavedItem(itemId)
|
||||
}
|
||||
}
|
||||
|
||||
SavedItemAction.Archive -> {
|
||||
viewModelScope.launch {
|
||||
dataService.archiveSavedItem(itemID)
|
||||
dataService.archiveSavedItem(itemId)
|
||||
}
|
||||
}
|
||||
|
||||
SavedItemAction.Unarchive -> {
|
||||
viewModelScope.launch {
|
||||
dataService.unarchiveSavedItem(itemID)
|
||||
dataService.unarchiveSavedItem(itemId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ import app.omnivore.omnivore.feature.auth.LoginViewModel
|
||||
import app.omnivore.omnivore.feature.auth.WelcomeScreen
|
||||
import app.omnivore.omnivore.feature.components.LabelsViewModel
|
||||
import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
|
||||
import app.omnivore.omnivore.feature.following.FollowingScreen
|
||||
import app.omnivore.omnivore.feature.library.LibraryView
|
||||
import app.omnivore.omnivore.feature.library.SearchView
|
||||
import app.omnivore.omnivore.feature.library.SearchViewModel
|
||||
@ -134,7 +135,7 @@ fun PrimaryNavigator(
|
||||
}
|
||||
|
||||
composable(Routes.Following.route) {
|
||||
LibraryView(
|
||||
FollowingScreen(
|
||||
navController = navController,
|
||||
labelsViewModel = labelsViewModel,
|
||||
saveViewModel = saveViewModel,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#Wed Feb 14 01:21:10 GMT 2024
|
||||
#Wed Apr 17 23:59:51 CEST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Reference in New Issue
Block a user