use search api to fill gaps sync might miss
This commit is contained in:
@ -4,6 +4,39 @@ import android.util.Log
|
||||
import app.omnivore.omnivore.networking.*
|
||||
import app.omnivore.omnivore.persistence.entities.*
|
||||
|
||||
suspend fun DataService.librarySearch(cursor: String?, query: String): SavedItemSyncResult {
|
||||
val searchResult = networker.search(cursor = cursor, limit = 10, query = query)
|
||||
|
||||
val savedItems = searchResult.items.map { it.item }
|
||||
|
||||
db.savedItemDao().insertAll(savedItems)
|
||||
|
||||
val labels: MutableList<SavedItemLabel> = mutableListOf()
|
||||
val crossRefs: MutableList<SavedItemAndSavedItemLabelCrossRef> = mutableListOf()
|
||||
|
||||
// save labels
|
||||
for (searchItem in searchResult.items) {
|
||||
labels.addAll(searchItem.labels)
|
||||
|
||||
val newCrossRefs = searchItem.labels.map {
|
||||
SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = searchItem.item.savedItemId)
|
||||
}
|
||||
|
||||
crossRefs.addAll(newCrossRefs)
|
||||
}
|
||||
|
||||
db.savedItemLabelDao().insertAll(labels)
|
||||
db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs)
|
||||
|
||||
return SavedItemSyncResult(
|
||||
hasError = false,
|
||||
hasMoreItems = false,
|
||||
cursor = searchResult.cursor,
|
||||
count = searchResult.items.size,
|
||||
savedItemSlugs = savedItems.map { it.slug }
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): SavedItemSyncResult {
|
||||
val syncResult = networker.savedItemUpdates(cursor = cursor, limit = limit, since = since) ?: return SavedItemSyncResult.errorResult
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@ package app.omnivore.omnivore.networking
|
||||
|
||||
import app.omnivore.omnivore.graphql.generated.SearchQuery
|
||||
import app.omnivore.omnivore.graphql.generated.TypeaheadSearchQuery
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItem
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemCardData
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
|
||||
data class SearchQueryResponse(
|
||||
@ -10,6 +12,16 @@ data class SearchQueryResponse(
|
||||
val cardsData: List<SavedItemCardData>
|
||||
)
|
||||
|
||||
data class LibrarySearchQueryResponse(
|
||||
val cursor: String?,
|
||||
val items: List<LibrarySearchItem>
|
||||
)
|
||||
|
||||
data class LibrarySearchItem(
|
||||
val item: SavedItem,
|
||||
val labels: List<SavedItemLabel>
|
||||
)
|
||||
|
||||
suspend fun Networker.typeaheadSearch(
|
||||
query: String
|
||||
): SearchQueryResponse {
|
||||
@ -44,7 +56,7 @@ suspend fun Networker.search(
|
||||
cursor: String? = null,
|
||||
limit: Int = 15,
|
||||
query: String
|
||||
): SearchQueryResponse {
|
||||
): LibrarySearchQueryResponse {
|
||||
try {
|
||||
val result = authenticatedApolloClient().query(
|
||||
SearchQuery(
|
||||
@ -57,22 +69,46 @@ suspend fun Networker.search(
|
||||
val newCursor = result.data?.search?.onSearchSuccess?.pageInfo?.endCursor
|
||||
val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf()
|
||||
|
||||
val cardsData = itemList.map {
|
||||
SavedItemCardData(
|
||||
savedItemId = it.node.id,
|
||||
slug = it.node.slug,
|
||||
publisherURLString = it.node.originalArticleUrl,
|
||||
title = it.node.title,
|
||||
author = it.node.author,
|
||||
imageURLString = it.node.image,
|
||||
isArchived = it.node.isArchived,
|
||||
pageURLString = it.node.url,
|
||||
contentReader = it.node.contentReader.rawValue,
|
||||
val searchItems = itemList.map {
|
||||
LibrarySearchItem(
|
||||
item = SavedItem(
|
||||
savedItemId = it.node.id,
|
||||
title = it.node.title,
|
||||
createdAt = it.node.createdAt as String,
|
||||
savedAt = it.node.savedAt as String,
|
||||
readAt = it.node.readAt as String?,
|
||||
updatedAt = it.node.updatedAt as String?,
|
||||
readingProgress = it.node.readingProgressPercent,
|
||||
readingProgressAnchor = it.node.readingProgressAnchorIndex,
|
||||
imageURLString = it.node.image,
|
||||
pageURLString = it.node.url,
|
||||
descriptionText = it.node.description,
|
||||
publisherURLString = it.node.originalArticleUrl,
|
||||
siteName = it.node.siteName,
|
||||
author = it.node.author,
|
||||
publishDate = it.node.publishedAt as String?,
|
||||
slug = it.node.slug,
|
||||
isArchived = it.node.isArchived,
|
||||
contentReader = it.node.contentReader.rawValue,
|
||||
content = null
|
||||
),
|
||||
labels = (it.node.labels ?: listOf()).map { label ->
|
||||
SavedItemLabel(
|
||||
savedItemLabelId = label.id,
|
||||
name = label.name,
|
||||
color = label.color,
|
||||
createdAt = null,
|
||||
labelDescription = null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return SearchQueryResponse(newCursor, cardsData)
|
||||
return LibrarySearchQueryResponse(
|
||||
cursor = newCursor,
|
||||
items = searchItems
|
||||
)
|
||||
} catch (e: java.lang.Exception) {
|
||||
return SearchQueryResponse(null, listOf())
|
||||
return LibrarySearchQueryResponse(null, listOf())
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +109,11 @@ fun LibraryViewContent(libraryViewModel: LibraryViewModel, modifier: Modifier) {
|
||||
}
|
||||
|
||||
InfiniteListHandler(listState = listState) {
|
||||
libraryViewModel.load()
|
||||
if (cardsData.isEmpty()) {
|
||||
libraryViewModel.load()
|
||||
} else {
|
||||
libraryViewModel.loadUsingSearchAPI()
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
|
||||
@ -25,6 +25,7 @@ class LibraryViewModel @Inject constructor(
|
||||
private val datastoreRepo: DatastoreRepository
|
||||
): ViewModel() {
|
||||
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
|
||||
@ -108,6 +109,21 @@ class LibraryViewModel @Inject constructor(
|
||||
performSearch(clearPreviousSearch)
|
||||
} else {
|
||||
syncItems()
|
||||
loadUsingSearchAPI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadUsingSearchAPI() {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val result = dataService.librarySearch(cursor = librarySearchCursor, query = searchQueryString())
|
||||
result.cursor?.let {
|
||||
librarySearchCursor = it
|
||||
}
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,6 +152,7 @@ class LibraryViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun handleFilterChanges() {
|
||||
librarySearchCursor = null
|
||||
if (searchTextLiveData.value != "") {
|
||||
performSearch(true)
|
||||
} else if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) {
|
||||
@ -194,7 +211,7 @@ class LibraryViewModel @Inject constructor(
|
||||
searchIdx += 1
|
||||
|
||||
// Execute the search
|
||||
val searchResult = networker.typeaheadSearch(searchQueryString())
|
||||
val searchResult = networker.typeaheadSearch(searchTextLiveData.value ?: "")
|
||||
|
||||
// Search results aren't guaranteed to return in order so this
|
||||
// will discard old results that are returned while a user is typing.
|
||||
@ -237,16 +254,14 @@ class LibraryViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun searchQueryString(): String {
|
||||
return searchTextLiveData.value ?: ""
|
||||
// Unused code for typeahead search
|
||||
// var query = "${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
// val searchText = searchTextLiveData.value ?: ""
|
||||
//
|
||||
// if (searchText.isNotEmpty()) {
|
||||
// query += " $searchText"
|
||||
// }
|
||||
//
|
||||
// return query
|
||||
var query = "${appliedFilterLiveData.value?.queryString} ${appliedSortFilterLiveData.value?.queryString}"
|
||||
val searchText = searchTextLiveData.value ?: ""
|
||||
|
||||
if (searchText.isNotEmpty()) {
|
||||
query += " $searchText"
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user