diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/HighlightActionHandlers.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/HighlightActionHandlers.kt index bc2d8e9a7..c4e7700ac 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/HighlightActionHandlers.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/HighlightActionHandlers.kt @@ -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) + } } - } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt index dabe14122..f3ffba8a6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/LibrarySync.kt @@ -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) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/ReadingProgressChangeHandler.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/ReadingProgressChangeHandler.kt index 4f06bc07e..319340b92 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/ReadingProgressChangeHandler.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/ReadingProgressChangeHandler.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SavedItemMenuActionHandlers.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SavedItemMenuActionHandlers.kt index 471e5afb7..c6246c786 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SavedItemMenuActionHandlers.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SavedItemMenuActionHandlers.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SyncOfflineChanges.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SyncOfflineChanges.kt index c818af698..7c217a8e2 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SyncOfflineChanges.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/SyncOfflineChanges.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/model/LibraryQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/model/LibraryQuery.kt new file mode 100644 index 000000000..fe33fa46b --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/model/LibraryQuery.kt @@ -0,0 +1,9 @@ +package app.omnivore.omnivore.core.data.model + +data class LibraryQuery( + val allowedArchiveStates: List, + val sortKey: String, + val requiredLabels: List, + val excludedLabels: List, + val allowedContentReaders: List +) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/model/ServerSyncStatus.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/model/ServerSyncStatus.kt similarity index 79% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/model/ServerSyncStatus.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/model/ServerSyncStatus.kt index 2bbb6c930..dbff112b3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/model/ServerSyncStatus.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/model/ServerSyncStatus.kt @@ -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), diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/LibraryRepository.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/LibraryRepository.kt new file mode 100644 index 000000000..0dd64d275 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/data/repository/LibraryRepository.kt @@ -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> = + dataService.db.savedItemDao().filteredLibraryData( + query.allowedArchiveStates, + query.sortKey, + query.requiredLabels, + query.excludedLabels, + query.allowedContentReaders + ) + +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/Highlight.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/Highlight.kt index 6d52e6203..2086cebe9 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/Highlight.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/Highlight.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/HighlightChange.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/HighlightChange.kt index 33be6c46c..8a09cb0cb 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/HighlightChange.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/HighlightChange.kt @@ -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 diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItem.kt index 33df145a5..f47198316 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItem.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItem.kt @@ -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, excludedLabels: List, allowedContentReaders: List - ): LiveData> + ): Flow> fun filteredLibraryData( allowedArchiveStates: List, @@ -224,7 +224,7 @@ interface SavedItemDao { requiredLabels: List, excludedLabels: List, allowedContentReaders: List - ): LiveData> { + ): Flow> { return _filteredLibraryData( allowedArchiveStates = allowedArchiveStates, sortKey = sortKey, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItemLabel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItemLabel.kt index 485238ff5..f65515c3b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItemLabel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/database/entities/SavedItemLabel.kt @@ -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( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt index 31664e652..f12a73a42 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/network/SearchQuery.kt @@ -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( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/di/AppModule.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/di/AppModule.kt index 70de06127..2e5bd7ad1 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/di/AppModule.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/di/AppModule.kt @@ -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) + } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/components/LabelsViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/components/LabelsViewModel.kt index ecc1bc741..212446e5f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/components/LabelsViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/components/LabelsViewModel.kt @@ -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 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 4c9c317ec..379dbbb0f 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,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 by viewModel.activeLabelsLiveData.observeAsState(listOf()) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryNavigationBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryNavigationBar.kt index a65973a40..74ae4438d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryNavigationBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryNavigationBar.kt @@ -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( 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 c45230d77..211df85d1 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 @@ -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 +) { 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 by libraryViewModel.itemsLiveData.observeAsState( +/* val cardsData: List 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) 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 a18e33ef3..4812d9434 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 @@ -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(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(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>() + + val uiState: StateFlow = _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>() val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX) val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST) val bottomSheetState = MutableLiveData(LibraryBottomSheetState.HIDDEN) - val currentItemLiveData = MutableLiveData(null) + val currentItem = mutableStateOf(null) val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData() val activeLabelsLiveData = MutableLiveData>(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, + ) : LibraryUiState + + data object Error : LibraryUiState +} + +enum class SavedItemAction { + Delete, Archive, Unarchive, EditLabels, EditInfo, MarkRead, MarkUnread } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt index 43848ceeb..f5bcc8680 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/reader/WebReaderViewModel.kt @@ -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 { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt index 42643e2f3..f68b250b8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt @@ -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,