add library repository and stateflow

This commit is contained in:
Stefano Sansone
2024-02-17 20:27:18 +00:00
parent f9a122d571
commit 4a285bd44f
21 changed files with 268 additions and 230 deletions

View File

@ -1,176 +1,185 @@
package app.omnivore.omnivore.core.data
import android.util.Log
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.core.database.entities.saveHighlightChange
import app.omnivore.omnivore.core.network.CreateHighlightParams
import app.omnivore.omnivore.core.network.DeleteHighlightParams
import app.omnivore.omnivore.core.network.MergeHighlightsParams
import app.omnivore.omnivore.core.network.UpdateHighlightParams
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.core.database.entities.saveHighlightChange
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
import java.util.UUID
suspend fun DataService.createWebHighlight(jsonString: String, colorName: String?) {
val createHighlightInput = Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput()
val createHighlightInput =
Gson().fromJson(jsonString, CreateHighlightParams::class.java).asCreateHighlightInput()
withContext(Dispatchers.IO) {
val highlight = Highlight(
type = "HIGHLIGHT",
highlightId = createHighlightInput.id,
shortId = createHighlightInput.shortId,
quote = createHighlightInput.quote.getOrNull(),
prefix = null,
suffix = null,
patch = createHighlightInput.patch.getOrNull(),
annotation = createHighlightInput.annotation.getOrNull(),
createdAt = null,
updatedAt = null,
createdByMe = false,
color = colorName ?: createHighlightInput.color.getOrNull(),
highlightPositionPercent = createHighlightInput.highlightPositionPercent.getOrNull() ?: 0.0,
highlightPositionAnchorIndex = createHighlightInput.highlightPositionAnchorIndex.getOrNull() ?: 0
)
withContext(Dispatchers.IO) {
val highlight = Highlight(
type = "HIGHLIGHT",
highlightId = createHighlightInput.id,
shortId = createHighlightInput.shortId,
quote = createHighlightInput.quote.getOrNull(),
prefix = null,
suffix = null,
patch = createHighlightInput.patch.getOrNull(),
annotation = createHighlightInput.annotation.getOrNull(),
createdAt = null,
updatedAt = null,
createdByMe = false,
color = colorName ?: createHighlightInput.color.getOrNull(),
highlightPositionPercent = createHighlightInput.highlightPositionPercent.getOrNull()
?: 0.0,
highlightPositionAnchorIndex = createHighlightInput.highlightPositionAnchorIndex.getOrNull()
?: 0
)
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
val highlightChange = saveHighlightChange(db.highlightChangesDao(), createHighlightInput.articleId, highlight)
val highlightChange =
saveHighlightChange(db.highlightChangesDao(), createHighlightInput.articleId, highlight)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = createHighlightInput.id,
savedItemId = createHighlightInput.articleId
)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = createHighlightInput.id, savedItemId = createHighlightInput.articleId
)
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
performHighlightChange(highlightChange)
}
performHighlightChange(highlightChange)
}
}
suspend fun DataService.createNoteHighlight(savedItemId: String, note: String): String {
val shortId = NanoId.generate(size = 14)
val createHighlightId = UUID.randomUUID().toString()
val shortId = NanoId.generate(size = 14)
val createHighlightId = UUID.randomUUID().toString()
withContext(Dispatchers.IO) {
val highlight = Highlight(
type = "NOTE",
highlightId = createHighlightId,
shortId = shortId,
quote = null,
prefix = null,
suffix = null,
patch =null,
annotation = note,
createdAt = null,
updatedAt = null,
createdByMe = true,
color = null,
highlightPositionAnchorIndex = 0,
highlightPositionPercent = 0.0
)
withContext(Dispatchers.IO) {
val highlight = Highlight(
type = "NOTE",
highlightId = createHighlightId,
shortId = shortId,
quote = null,
prefix = null,
suffix = null,
patch = null,
annotation = note,
createdAt = null,
updatedAt = null,
createdByMe = true,
color = null,
highlightPositionAnchorIndex = 0,
highlightPositionPercent = 0.0
)
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = createHighlightId,
savedItemId = savedItemId
)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = createHighlightId, savedItemId = savedItemId
)
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
performHighlightChange(highlightChange)
}
performHighlightChange(highlightChange)
}
return createHighlightId
return createHighlightId
}
suspend fun DataService.mergeWebHighlights(jsonString: String) {
val mergeHighlightInput = Gson().fromJson(jsonString, MergeHighlightsParams::class.java).asMergeHighlightInput()
Log.d("sync", "mergeHighlightInput: " + mergeHighlightInput.id + ": " + mergeHighlightInput)
val mergeHighlightInput =
Gson().fromJson(jsonString, MergeHighlightsParams::class.java).asMergeHighlightInput()
Log.d("sync", "mergeHighlightInput: " + mergeHighlightInput.id + ": " + mergeHighlightInput)
withContext(Dispatchers.IO) {
val highlight = Highlight(
type = "HIGHLIGHT",
highlightId = mergeHighlightInput.id,
shortId = mergeHighlightInput.shortId,
quote = mergeHighlightInput.quote,
prefix = null,
suffix = null,
patch = mergeHighlightInput.patch,
annotation = mergeHighlightInput.annotation.getOrNull(),
createdAt = null,
updatedAt = null,
createdByMe = false,
color = mergeHighlightInput.color.getOrNull(),
highlightPositionPercent = mergeHighlightInput.highlightPositionPercent.getOrNull() ?: 0.0,
highlightPositionAnchorIndex = mergeHighlightInput.highlightPositionAnchorIndex.getOrNull() ?: 0
)
withContext(Dispatchers.IO) {
val highlight = Highlight(
type = "HIGHLIGHT",
highlightId = mergeHighlightInput.id,
shortId = mergeHighlightInput.shortId,
quote = mergeHighlightInput.quote,
prefix = null,
suffix = null,
patch = mergeHighlightInput.patch,
annotation = mergeHighlightInput.annotation.getOrNull(),
createdAt = null,
updatedAt = null,
createdByMe = false,
color = mergeHighlightInput.color.getOrNull(),
highlightPositionPercent = mergeHighlightInput.highlightPositionPercent.getOrNull()
?: 0.0,
highlightPositionAnchorIndex = mergeHighlightInput.highlightPositionAnchorIndex.getOrNull()
?: 0
)
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_MERGE.rawValue
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_MERGE.rawValue
val highlightChange = saveHighlightChange(
db.highlightChangesDao(),
mergeHighlightInput.articleId,
highlight,
html = mergeHighlightInput.html.getOrNull(),
overlappingIDs = mergeHighlightInput.overlapHighlightIdList
)
val highlightChange = saveHighlightChange(
db.highlightChangesDao(),
mergeHighlightInput.articleId,
highlight,
html = mergeHighlightInput.html.getOrNull(),
overlappingIDs = mergeHighlightInput.overlapHighlightIdList
)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = mergeHighlightInput.id,
savedItemId = mergeHighlightInput.articleId
)
val crossRef = SavedItemAndHighlightCrossRef(
highlightId = mergeHighlightInput.id, savedItemId = mergeHighlightInput.articleId
)
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
db.highlightDao().insertAll(listOf(highlight))
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
Log.d("sync", "Setting up highlight merge")
performHighlightChange(highlightChange)
}
Log.d("sync", "Setting up highlight merge")
performHighlightChange(highlightChange)
}
}
suspend fun DataService.updateWebHighlight(jsonString: String) {
val updateHighlightParams = Gson().fromJson(jsonString, UpdateHighlightParams::class.java)
val updateHighlightParams = Gson().fromJson(jsonString, UpdateHighlightParams::class.java)
if (updateHighlightParams.highlightId == null || updateHighlightParams.libraryItemId == null) {
Log.d("error","ERROR INVALID HIGHLIGHT DATA")
return
}
if (updateHighlightParams.highlightId == null || updateHighlightParams.libraryItemId == null) {
Log.d("error", "ERROR INVALID HIGHLIGHT DATA")
return
}
withContext(Dispatchers.IO) {
val highlight = db.highlightDao().findById(highlightId = updateHighlightParams.highlightId ?: "") ?: return@withContext
withContext(Dispatchers.IO) {
val highlight =
db.highlightDao().findById(highlightId = updateHighlightParams.highlightId)
?: return@withContext
highlight.annotation = updateHighlightParams.annotation
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_UPDATE.rawValue
db.highlightDao().update(highlight)
highlight.annotation = updateHighlightParams.annotation
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_UPDATE.rawValue
db.highlightDao().update(highlight)
val highlightChange = saveHighlightChange(db.highlightChangesDao(), updateHighlightParams.libraryItemId ?: "", highlight)
performHighlightChange(highlightChange)
}
val highlightChange = saveHighlightChange(
db.highlightChangesDao(), updateHighlightParams.libraryItemId, highlight
)
performHighlightChange(highlightChange)
}
}
suspend fun DataService.deleteHighlightFromJSON(jsonString: String) {
val deleteHighlightParams = Gson().fromJson(jsonString, DeleteHighlightParams::class.java)
deleteHighlight(deleteHighlightParams.libraryItemId, deleteHighlightParams.highlightId)
val deleteHighlightParams = Gson().fromJson(jsonString, DeleteHighlightParams::class.java)
deleteHighlight(deleteHighlightParams.libraryItemId, deleteHighlightParams.highlightId)
}
private suspend fun DataService.deleteHighlight(savedItemId: String, highlightID: String) {
withContext(Dispatchers.IO) {
val highlight = db.highlightDao().findById(highlightId = highlightID)
withContext(Dispatchers.IO) {
val highlight = db.highlightDao().findById(highlightId = highlightID)
highlight?.let {
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue
db.highlightDao().update(highlight)
highlight?.let {
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue
db.highlightDao().update(highlight)
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
performHighlightChange(highlightChange)
val highlightChange =
saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
performHighlightChange(highlightChange)
}
}
}
}

View File

@ -8,7 +8,7 @@ import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighli
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.core.network.savedItemUpdates
import app.omnivore.omnivore.core.network.search
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
suspend fun DataService.librarySearch(cursor: String?, query: String): SearchResult {
val searchResult = networker.search(cursor = cursor, limit = 10, query = query)

View File

@ -1,6 +1,6 @@
package app.omnivore.omnivore.core.data
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import app.omnivore.omnivore.core.network.ReadingProgressParams
import app.omnivore.omnivore.core.network.updateReadingProgress
import com.google.gson.Gson

View File

@ -1,6 +1,6 @@
package app.omnivore.omnivore.core.data
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import app.omnivore.omnivore.core.network.archiveSavedItem
import app.omnivore.omnivore.core.network.deleteSavedItem
import app.omnivore.omnivore.core.network.unarchiveSavedItem

View File

@ -13,7 +13,7 @@ import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
import app.omnivore.omnivore.graphql.generated.type.HighlightType
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.HighlightChange
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.highlightChangeToHighlight

View File

@ -0,0 +1,9 @@
package app.omnivore.omnivore.core.data.model
data class LibraryQuery(
val allowedArchiveStates: List<Int>,
val sortKey: String,
val requiredLabels: List<String>,
val excludedLabels: List<String>,
val allowedContentReaders: List<String>
)

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.core.model
package app.omnivore.omnivore.core.data.model
enum class ServerSyncStatus(val rawValue: Int) {
IS_SYNCED(0),

View File

@ -0,0 +1,21 @@
package app.omnivore.omnivore.core.data.repository
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.model.LibraryQuery
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class LibraryRepository @Inject constructor(
private val dataService: DataService,
) {
fun getSavedItems(query: LibraryQuery): Flow<List<SavedItemWithLabelsAndHighlights>> =
dataService.db.savedItemDao().filteredLibraryData(
query.allowedArchiveStates,
query.sortKey,
query.requiredLabels,
query.excludedLabels,
query.allowedContentReaders
)
}

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.core.database.entities
import androidx.room.*
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import com.google.gson.annotations.SerializedName

View File

@ -9,7 +9,7 @@ import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

View File

@ -3,7 +3,7 @@ package app.omnivore.omnivore.core.database.entities
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.room.*
import java.util.*
import kotlinx.coroutines.flow.Flow
@Entity
data class SavedItem(
@ -216,7 +216,7 @@ interface SavedItemDao {
requiredLabels: List<String>,
excludedLabels: List<String>,
allowedContentReaders: List<String>
): LiveData<List<SavedItemWithLabelsAndHighlights>>
): Flow<List<SavedItemWithLabelsAndHighlights>>
fun filteredLibraryData(
allowedArchiveStates: List<Int>,
@ -224,7 +224,7 @@ interface SavedItemDao {
requiredLabels: List<String>,
excludedLabels: List<String>,
allowedContentReaders: List<String>
): LiveData<List<SavedItemWithLabelsAndHighlights>> {
): Flow<List<SavedItemWithLabelsAndHighlights>> {
return _filteredLibraryData(
allowedArchiveStates = allowedArchiveStates,
sortKey = sortKey,

View File

@ -2,7 +2,7 @@ package app.omnivore.omnivore.core.database.entities
import androidx.lifecycle.LiveData
import androidx.room.*
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
@Entity
data class SavedItemLabel(

View File

@ -4,7 +4,7 @@ import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.graphql.generated.SearchQuery
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import com.apollographql.apollo3.api.Optional
data class LibrarySearchQueryResponse(

View File

@ -1,10 +1,10 @@
package app.omnivore.omnivore.di
import android.content.Context
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.analytics.EventTracker
import app.omnivore.omnivore.core.datastore.OmnivoreDatastore
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.datastore.OmnivoreDatastore
import app.omnivore.omnivore.core.network.Networker
import dagger.Module
import dagger.Provides
@ -37,4 +37,5 @@ object AppModule {
@ApplicationContext app: Context,
networker: Networker
) = DataService(app, networker)
}

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.feature.components
import androidx.lifecycle.*
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.data.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.LocalDate

View File

@ -16,12 +16,15 @@ 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 app.omnivore.omnivore.R
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.feature.components.LabelChipColors
@Composable
fun LibraryFilterBar(viewModel: LibraryViewModel) {
fun LibraryFilterBar(
viewModel: LibraryViewModel = hiltViewModel()
) {
var isSavedItemFilterMenuExpanded by remember { mutableStateOf(false) }
val activeSavedItemFilter: SavedItemFilter by viewModel.appliedFilterLiveData.observeAsState(SavedItemFilter.INBOX)
val activeLabels: List<SavedItemLabel> by viewModel.activeLabelsLiveData.observeAsState(listOf())

View File

@ -131,7 +131,7 @@ fun LibraryNavigationBar(
contentDescription = null
)
}
/* IconButton(onClick = { isMenuExpanded = true } ) {
IconButton(onClick = { isMenuExpanded = true } ) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null
@ -144,7 +144,7 @@ fun LibraryNavigationBar(
onDismiss = { isMenuExpanded = false },
)
}
}*/
}
} ?: run {
IconButton(onClick = onSearchClicked) {
Icon(

View File

@ -52,6 +52,8 @@ import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
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
@ -71,7 +73,7 @@ import kotlinx.coroutines.launch
@Composable
fun LibraryView(
libraryViewModel: LibraryViewModel,
libraryViewModel: LibraryViewModel = hiltViewModel(),
labelsViewModel: LabelsViewModel,
saveViewModel: SaveViewModel,
editInfoViewModel: EditInfoViewModel,
@ -81,6 +83,8 @@ fun LibraryView(
val coroutineScope = rememberCoroutineScope()
val uiState by libraryViewModel.uiState.collectAsStateWithLifecycle()
val showBottomSheet: LibraryBottomSheetState by libraryViewModel.bottomSheetState.observeAsState(
LibraryBottomSheetState.HIDDEN
)
@ -132,11 +136,17 @@ fun LibraryView(
)
},
) { paddingValues ->
LibraryViewContent(
libraryViewModel,
modifier = Modifier
.padding(top = paddingValues.calculateTopPadding())
)
when (uiState) {
is LibraryUiState.Success -> {
LibraryViewContent(
libraryViewModel,
modifier = Modifier
.padding(top = paddingValues.calculateTopPadding()),
cardsData = (uiState as LibraryUiState.Success).items
)
}
else -> {}
}
}
}
@ -169,7 +179,7 @@ fun LabelBottomSheet(
labelsViewModel = labelsViewModel,
initialSelectedLabels = currentSavedItemData.labels,
onCancel = {
libraryViewModel.currentItemLiveData.value = null
libraryViewModel.currentItem.value = null
onDismiss()
},
isLibraryMode = false,
@ -180,7 +190,7 @@ fun LabelBottomSheet(
labels = it
)
}
libraryViewModel.currentItemLiveData.value = null
libraryViewModel.currentItem.value = null
onDismiss()
},
onCreateLabel = { newLabelName, labelHexValue ->
@ -196,7 +206,7 @@ fun LabelBottomSheet(
isLibraryMode = true,
onSave = {
libraryViewModel.updateAppliedLabels(it)
libraryViewModel.currentItemLiveData.value = null
libraryViewModel.currentItem.value = null
onDismiss()
},
onCreateLabel = { newLabelName, labelHexValue ->
@ -257,11 +267,11 @@ fun EditBottomSheet(
description = currentSavedItemData?.savedItem?.descriptionText,
viewModel = editInfoViewModel,
onCancel = {
libraryViewModel.currentItemLiveData.value = null
libraryViewModel.currentItem.value = null
onDismiss()
},
onUpdated = {
libraryViewModel.currentItemLiveData.value = null
libraryViewModel.currentItem.value = null
libraryViewModel.refresh()
onDismiss()
}
@ -272,7 +282,11 @@ fun EditBottomSheet(
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
fun LibraryViewContent(
libraryViewModel: LibraryViewModel,
modifier: Modifier,
cardsData: List<SavedItemWithLabelsAndHighlights>
) {
val context = LocalContext.current
val listState = rememberLazyListState()
@ -282,9 +296,9 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
)
val selectedItem: SavedItemWithLabelsAndHighlights? by libraryViewModel.actionsMenuItemLiveData.observeAsState()
val cardsData: List<SavedItemWithLabelsAndHighlights> by libraryViewModel.itemsLiveData.observeAsState(
/* val cardsData: List<SavedItemWithLabelsAndHighlights> by libraryViewModel.itemsLiveData.observeAsState(
listOf()
)
)*/
Box(
modifier = Modifier
@ -299,7 +313,6 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
modifier = modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.padding(horizontal = 6.dp)
) {
item {
LibraryFilterBar(libraryViewModel)

View File

@ -7,8 +7,6 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.utils.DatastoreKeys
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.archiveSavedItem
@ -16,64 +14,82 @@ import app.omnivore.omnivore.core.data.deleteSavedItem
import app.omnivore.omnivore.core.data.fetchSavedItemContent
import app.omnivore.omnivore.core.data.isSavedItemContentStoredInDB
import app.omnivore.omnivore.core.data.librarySearch
import app.omnivore.omnivore.core.data.model.LibraryQuery
import app.omnivore.omnivore.core.data.repository.LibraryRepository
import app.omnivore.omnivore.core.data.sync
import app.omnivore.omnivore.core.data.syncLabels
import app.omnivore.omnivore.core.data.syncOfflineItemsWithServerIfNeeded
import app.omnivore.omnivore.core.data.unarchiveSavedItem
import app.omnivore.omnivore.core.data.updateWebReadingProgress
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.createNewLabel
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.core.network.Networker
import app.omnivore.omnivore.core.network.createNewLabel
import app.omnivore.omnivore.feature.ResourceProvider
import app.omnivore.omnivore.feature.setSavedItemLabels
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.utils.DatastoreKeys
import com.apollographql.apollo3.api.Optional
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
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 LibraryViewModel @Inject constructor(
private val networker: Networker,
private val dataService: DataService,
private val datastoreRepo: DatastoreRepository,
private val resourceProvider: ResourceProvider
private val resourceProvider: ResourceProvider,
private val libraryRepository: LibraryRepository,
) : ViewModel(), SavedItemViewModel {
private val contentRequestChannel = Channel<String>(capacity = Channel.UNLIMITED)
private var cursor: String? = null
private var librarySearchCursor: String? = null
// These are used to make sure we handle search result
// responses in the right order
private var searchIdx = 0
private var receivedIdx = 0
var snackbarMessage by mutableStateOf<String?>(null)
private set
// Live Data
private var itemsLiveDataInternal = dataService.db.savedItemDao().filteredLibraryData(
allowedArchiveStates = listOf(0),
sortKey = "newest",
requiredLabels = listOf(),
excludedLabels = listOf(),
allowedContentReaders = listOf("WEB", "PDF", "EPUB")
private val _libraryQuery = MutableStateFlow(
LibraryQuery(
allowedArchiveStates = listOf(0),
sortKey = "newest",
requiredLabels = listOf(),
excludedLabels = listOf(),
allowedContentReaders = listOf("WEB", "PDF", "EPUB")
)
)
val itemsLiveData = MediatorLiveData<List<SavedItemWithLabelsAndHighlights>>()
val uiState: StateFlow<LibraryUiState> = _libraryQuery.flatMapLatest { query ->
libraryRepository.getSavedItems(query)
}.map(LibraryUiState::Success).stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000), // Adjust as needed
initialValue = LibraryUiState.Loading
)
private val itemsLiveData = MediatorLiveData<List<SavedItemWithLabelsAndHighlights>>()
val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX)
val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST)
val bottomSheetState = MutableLiveData(LibraryBottomSheetState.HIDDEN)
val currentItemLiveData = MutableLiveData<String?>(null)
val currentItem = mutableStateOf<String?>(null)
val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData()
val activeLabelsLiveData = MutableLiveData<List<SavedItemLabel>>(listOf())
@ -130,8 +146,6 @@ class LibraryViewModel @Inject constructor(
hasLoadedInitialFilters = false
cursor = null
librarySearchCursor = null
searchIdx = 0
receivedIdx = 0
}
if (hasLoadedInitialFilters) {
@ -153,8 +167,7 @@ class LibraryViewModel @Inject constructor(
viewModelScope.launch {
withContext(Dispatchers.IO) {
val result = dataService.librarySearch(
cursor = librarySearchCursor,
query = searchQueryString()
cursor = librarySearchCursor, query = searchQueryString()
)
result.cursor?.let {
librarySearchCursor = it
@ -237,17 +250,13 @@ class LibraryViewModel @Inject constructor(
else -> listOf()
}
val newData = dataService.db.savedItemDao().filteredLibraryData(
_libraryQuery.value = LibraryQuery(
allowedArchiveStates = allowedArchiveStates,
sortKey = sortKey,
requiredLabels = requiredLabels,
excludedLabels = excludeLabels,
allowedContentReaders = allowedContentReaders
)
itemsLiveData.removeSource(itemsLiveDataInternal)
itemsLiveDataInternal = newData
itemsLiveData.addSource(itemsLiveDataInternal, itemsLiveData::setValue)
}
}
@ -316,12 +325,12 @@ class LibraryViewModel @Inject constructor(
}
SavedItemAction.EditLabels -> {
currentItemLiveData.value = itemID
currentItem.value = itemID
bottomSheetState.value = LibraryBottomSheetState.LABEL
}
SavedItemAction.EditInfo -> {
currentItemLiveData.value = itemID
currentItem.value = itemID
bottomSheetState.value = LibraryBottomSheetState.EDIT
}
@ -424,7 +433,7 @@ class LibraryViewModel @Inject constructor(
}
fun currentSavedItemUnderEdit(): SavedItemWithLabelsAndHighlights? {
currentItemLiveData.value?.let { itemID ->
currentItem.value?.let { itemID ->
return itemsLiveData.value?.first { it.savedItem.savedItemId == itemID }
}
@ -446,12 +455,16 @@ class LibraryViewModel @Inject constructor(
}
}
enum class SavedItemAction {
Delete,
Archive,
Unarchive,
EditLabels,
EditInfo,
MarkRead,
MarkUnread
sealed interface LibraryUiState {
data object Loading : LibraryUiState
data class Success(
val items: List<SavedItemWithLabelsAndHighlights>,
) : LibraryUiState
data object Error : LibraryUiState
}
enum class SavedItemAction {
Delete, Archive, Unarchive, EditLabels, EditInfo, MarkRead, MarkUnread
}

View File

@ -333,31 +333,11 @@ class WebReaderViewModel @Inject constructor(
}
SavedItemAction.MarkRead -> {
viewModelScope.launch {
dataService.updateWebReadingProgress(
jsonString = Gson().toJson(
mapOf(
"id" to itemID,
"readingProgressPercent" to 100.0,
"readingProgressAnchorIndex" to 0
)
)
)
}
// TODO
}
SavedItemAction.MarkUnread -> {
viewModelScope.launch {
dataService.updateWebReadingProgress(
jsonString = Gson().toJson(
mapOf(
"id" to itemID,
"readingProgressPercent" to 0,
"readingProgressAnchorIndex" to 0
)
)
)
}
// TODO
}
}
}
@ -381,14 +361,8 @@ class WebReaderViewModel @Inject constructor(
}
}
// fun setHighlightColor(color: HighlightColor) {
// CoroutineScope(Dispatchers.Main).launch {
// highlightColor.postValue(color)
// }
// }
fun handleIncomingWebMessage(actionID: String, jsonString: String) {
Log.d("sync", "incoming change: ${actionID}: ${jsonString}")
Log.d("sync", "incoming change: ${actionID}: $jsonString")
when (actionID) {
"createHighlight" -> {
viewModelScope.launch {

View File

@ -8,25 +8,23 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import app.omnivore.omnivore.navigation.Routes
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.library.LibraryView
import app.omnivore.omnivore.feature.library.LibraryViewModel
import app.omnivore.omnivore.feature.library.SearchView
import app.omnivore.omnivore.feature.library.SearchViewModel
import app.omnivore.omnivore.feature.save.SaveViewModel
import app.omnivore.omnivore.feature.settings.PolicyWebView
import app.omnivore.omnivore.feature.settings.SettingsView
import app.omnivore.omnivore.feature.settings.SettingsViewModel
import app.omnivore.omnivore.navigation.Routes
@Composable
fun RootView(
loginViewModel: LoginViewModel,
searchViewModel: SearchViewModel,
libraryViewModel: LibraryViewModel,
settingsViewModel: SettingsViewModel,
labelsViewModel: LabelsViewModel,
saveViewModel: SaveViewModel,
@ -39,7 +37,6 @@ fun RootView(
PrimaryNavigator(
loginViewModel = loginViewModel,
searchViewModel = searchViewModel,
libraryViewModel = libraryViewModel,
settingsViewModel = settingsViewModel,
labelsViewModel = labelsViewModel,
saveViewModel = saveViewModel,
@ -61,7 +58,6 @@ fun RootView(
@Composable
fun PrimaryNavigator(
loginViewModel: LoginViewModel,
libraryViewModel: LibraryViewModel,
searchViewModel: SearchViewModel,
settingsViewModel: SettingsViewModel,
labelsViewModel: LabelsViewModel,
@ -73,7 +69,6 @@ fun PrimaryNavigator(
NavHost(navController = navController, startDestination = Routes.Library.route) {
composable(Routes.Library.route) {
LibraryView(
libraryViewModel = libraryViewModel,
navController = navController,
labelsViewModel = labelsViewModel,
saveViewModel = saveViewModel,